From 7f2af659c957be241481393b34713548103afae7 Mon Sep 17 00:00:00 2001 From: Maximiliano Curia Date: Fri, 18 Nov 2016 15:05:56 +0000 Subject: [PATCH] Import kio_5.28.0.orig.tar.xz [dgit import orig kio_5.28.0.orig.tar.xz] --- .reviewboardrc | 4 + CMakeLists.txt | 135 + COPYING.LIB | 510 + KF5KIOConfig.cmake.in | 24 + README.md | 18 + autotests/CMakeLists.txt | 125 + autotests/accessmanagertest.cpp | 130 + autotests/clipboardupdatertest.cpp | 172 + autotests/clipboardupdatertest.h | 38 + autotests/dataprotocoltest.cpp | 336 + autotests/dataprotocoltest.h | 21 + autotests/deletejobtest.cpp | 110 + autotests/deletejobtest.h | 38 + autotests/dropjobtest.cpp | 488 + autotests/fakecomputer.xml | 387 + autotests/favicontest.cpp | 303 + autotests/fileundomanagertest.cpp | 688 ++ autotests/fileundomanagertest.h | 60 + autotests/globaltest.cpp | 116 + autotests/globaltest.h | 40 + autotests/http/CMakeLists.txt | 68 + autotests/http/httpauthenticationtest.cpp | 344 + autotests/http/httpauthenticationtest.h | 33 + autotests/http/httpfiltertest.cpp | 153 + autotests/http/httpheaderdispositiontest.cpp | 382 + autotests/http/httpheaderdispositiontest.h | 33 + autotests/http/httpheadertokenizetest.cpp | 196 + autotests/http/httpheadertokenizetest.h | 33 + autotests/http/httpobjecttest.cpp | 52 + autotests/http/httpobjecttest.h | 44 + autotests/http_jobtest.cpp | 111 + autotests/httpserver_p.cpp | 287 + autotests/httpserver_p.h | 179 + autotests/jobguitest.cpp | 97 + autotests/jobremotetest.cpp | 389 + autotests/jobremotetest.h | 92 + autotests/jobtest.cpp | 1751 +++ autotests/jobtest.h | 136 + autotests/kacltest.cpp | 218 + autotests/kacltest.h | 54 + autotests/kcookiejar/CMakeLists.txt | 19 + autotests/kcookiejar/cookie.test | 188 + autotests/kcookiejar/cookie_rfc.test | 172 + autotests/kcookiejar/cookie_saving.test | 434 + autotests/kcookiejar/cookie_session.test | 51 + autotests/kcookiejar/cookie_settings.test | 112 + autotests/kcookiejar/kcookiejartest.cpp | 320 + autotests/kdirlistertest.cpp | 1387 +++ autotests/kdirlistertest.h | 151 + autotests/kdirmodeltest.cpp | 1529 +++ autotests/kdirmodeltest.h | 118 + autotests/kdiroperatortest.cpp | 123 + autotests/kfilecopytomenutest.cpp | 183 + autotests/kfileitemactionstest.cpp | 64 + autotests/kfileitemactionstest.h | 33 + autotests/kfileitemtest.cpp | 613 ++ autotests/kfileitemtest.h | 67 + autotests/kfileplacesmodeltest.cpp | 698 ++ autotests/kfilewidgettest.cpp | 129 + autotests/kiotesthelper.h | 205 + autotests/klocalsocketservertest.cpp | 308 + autotests/klocalsocketservertest.h | 51 + autotests/klocalsockettest.cpp | 249 + autotests/klocalsockettest.h | 48 + autotests/kmountpointtest.cpp | 134 + autotests/kmountpointtest.h | 35 + autotests/knewfilemenutest.cpp | 194 + autotests/kprotocolinfotest.cpp | 181 + autotests/krununittest.cpp | 398 + autotests/krununittest.h | 55 + autotests/ktcpsockettest.cpp | 401 + autotests/ktcpsockettest.h | 71 + .../kurifiltersearchprovideractionstest.cpp | 84 + .../kurifiltersearchprovideractionstest.h | 34 + autotests/kurifiltertest.cpp | 459 + autotests/kurifiltertest.h | 57 + autotests/kurlcomboboxtest.cpp | 47 + autotests/kurlcomboboxtest.h | 35 + autotests/kurlcompletiontest.cpp | 388 + autotests/kurlnavigatortest.cpp | 265 + autotests/kurlnavigatortest.h | 54 + autotests/kurlrequestertest.cpp | 142 + autotests/listdirtest.cpp | 80 + autotests/listdirtest.h | 39 + autotests/mkpathjobtest.cpp | 148 + autotests/pastetest.cpp | 228 + autotests/pastetest.h | 45 + autotests/threadtest.cpp | 107 + autotests/udsentry_benchmark.cpp | 444 + autotests/udsentrytest.cpp | 224 + autotests/udsentrytest.h | 33 + autotests/upurltest.cpp | 57 + autotests/wronglocalsizes.zip | Bin 0 -> 325 bytes cmake/FindACL.cmake | 57 + cmake/FindGSSAPI.cmake | 100 + docs/CMakeLists.txt | 2 + docs/design.txt | 203 + docs/kcookiejar5/CMakeLists.txt | 1 + docs/kcookiejar5/man-kcookiejar5.8.docbook | 149 + docs/kioslave5/CMakeLists.txt | 9 + docs/kioslave5/data/CMakeLists.txt | 2 + docs/kioslave5/data/index.docbook | 54 + docs/kioslave5/file/CMakeLists.txt | 2 + docs/kioslave5/file/index.docbook | 27 + docs/kioslave5/ftp/CMakeLists.txt | 2 + docs/kioslave5/ftp/index.docbook | 50 + docs/kioslave5/help/CMakeLists.txt | 4 + .../help/documentationnotfound/CMakeLists.txt | 2 + .../help/documentationnotfound/index.docbook | 69 + docs/kioslave5/help/index.docbook | 24 + docs/kioslave5/http/CMakeLists.txt | 2 + docs/kioslave5/http/index.docbook | 35 + docs/kioslave5/mailto/CMakeLists.txt | 2 + docs/kioslave5/mailto/index.docbook | 87 + docs/kioslave5/telnet/CMakeLists.txt | 2 + docs/kioslave5/telnet/index.docbook | 24 + docs/kioslave5/webdav/CMakeLists.txt | 2 + docs/kioslave5/webdav/index.docbook | 73 + docs/krun-passing-slaves.txt | 28 + docs/metadata.txt | 188 + docs/pics/kpropertiesdialog.png | Bin 0 -> 14213 bytes docs/pics/kurlrequester.png | Bin 0 -> 1996 bytes metainfo.yaml | 28 + po/af/kio5.po | 9457 +++++++++++++++++ po/ar/kio5.po | 8405 +++++++++++++++ po/ast/kio5.po | 7377 +++++++++++++ po/be/kio5.po | 8079 ++++++++++++++ po/be@latin/kio5.po | 9290 ++++++++++++++++ po/bg/kio5.po | 8821 +++++++++++++++ po/bn/kio5.po | 8125 ++++++++++++++ po/br/kio5.po | 7892 ++++++++++++++ po/bs/kio5.po | 8256 ++++++++++++++ .../kcookiejar5/man-kcookiejar5.8.docbook | 225 + po/ca/docs/kioslave5/data/index.docbook | 69 + po/ca/docs/kioslave5/file/index.docbook | 31 + po/ca/docs/kioslave5/ftp/index.docbook | 45 + .../help/documentationnotfound/index.docbook | 75 + po/ca/docs/kioslave5/help/index.docbook | 27 + po/ca/docs/kioslave5/http/index.docbook | 52 + po/ca/docs/kioslave5/mailto/index.docbook | 130 + po/ca/docs/kioslave5/telnet/index.docbook | 27 + po/ca/docs/kioslave5/webdav/index.docbook | 85 + po/ca/kio5.po | 8199 ++++++++++++++ po/ca@valencia/kio5.po | 8196 ++++++++++++++ po/cs/kio5.po | 7904 ++++++++++++++ po/csb/kio5.po | 8691 +++++++++++++++ po/cy/kio5.po | 9357 ++++++++++++++++ po/da/kio5.po | 8686 +++++++++++++++ .../kcookiejar5/man-kcookiejar5.8.docbook | 225 + po/de/docs/kioslave5/data/index.docbook | 83 + po/de/docs/kioslave5/file/index.docbook | 45 + po/de/docs/kioslave5/ftp/index.docbook | 63 + .../help/documentationnotfound/index.docbook | 89 + po/de/docs/kioslave5/help/index.docbook | 41 + po/de/docs/kioslave5/http/index.docbook | 68 + po/de/docs/kioslave5/mailto/index.docbook | 144 + po/de/docs/kioslave5/telnet/index.docbook | 43 + po/de/docs/kioslave5/webdav/index.docbook | 99 + po/de/kio5.po | 9105 ++++++++++++++++ po/el/kio5.po | 8857 +++++++++++++++ po/en_GB/kio5.po | 8691 +++++++++++++++ po/eo/kio5.po | 8840 +++++++++++++++ .../kcookiejar5/man-kcookiejar5.8.docbook | 225 + po/es/docs/kioslave5/data/index.docbook | 97 + po/es/docs/kioslave5/file/index.docbook | 59 + po/es/docs/kioslave5/ftp/index.docbook | 77 + .../help/documentationnotfound/index.docbook | 103 + po/es/docs/kioslave5/help/index.docbook | 55 + po/es/docs/kioslave5/http/index.docbook | 66 + po/es/docs/kioslave5/mailto/index.docbook | 144 + po/es/docs/kioslave5/telnet/index.docbook | 41 + po/es/docs/kioslave5/webdav/index.docbook | 99 + po/es/kio5.po | 8277 +++++++++++++++ po/et/docs/kioslave5/data/index.docbook | 85 + po/et/docs/kioslave5/file/index.docbook | 45 + po/et/docs/kioslave5/ftp/index.docbook | 63 + po/et/docs/kioslave5/help/index.docbook | 41 + po/et/docs/kioslave5/http/index.docbook | 66 + po/et/docs/kioslave5/mailto/index.docbook | 144 + po/et/docs/kioslave5/telnet/index.docbook | 41 + po/et/docs/kioslave5/webdav/index.docbook | 99 + po/et/kio5.po | 8632 +++++++++++++++ po/eu/kio5.po | 8736 +++++++++++++++ po/fa/kio5.po | 9065 ++++++++++++++++ po/fi/kio5.po | 8626 +++++++++++++++ po/fr/kio5.po | 8790 +++++++++++++++ po/fy/kio5.po | 9374 ++++++++++++++++ po/ga/kio5.po | 8691 +++++++++++++++ po/gl/kio5.po | 8214 ++++++++++++++ po/gu/kio5.po | 7757 ++++++++++++++ po/he/kio5.po | 8544 +++++++++++++++ po/hi/kio5.po | 8393 +++++++++++++++ po/hr/kio5.po | 8954 ++++++++++++++++ po/hsb/kio5.po | 8754 +++++++++++++++ po/hu/kio5.po | 8990 ++++++++++++++++ po/ia/kio5.po | 8939 ++++++++++++++++ po/is/kio5.po | 8779 +++++++++++++++ .../kcookiejar5/man-kcookiejar5.8.docbook | 225 + po/it/docs/kioslave5/data/index.docbook | 83 + po/it/docs/kioslave5/file/index.docbook | 45 + po/it/docs/kioslave5/ftp/index.docbook | 63 + .../help/documentationnotfound/index.docbook | 89 + po/it/docs/kioslave5/help/index.docbook | 41 + po/it/docs/kioslave5/http/index.docbook | 66 + po/it/docs/kioslave5/mailto/index.docbook | 144 + po/it/docs/kioslave5/telnet/index.docbook | 41 + po/it/docs/kioslave5/webdav/index.docbook | 93 + po/it/kio5.po | 8789 +++++++++++++++ po/ja/kio5.po | 8288 +++++++++++++++ po/kk/kio5.po | 8538 +++++++++++++++ po/km/kio5.po | 8161 ++++++++++++++ po/ko/kio5.po | 7862 ++++++++++++++ po/ku/kio5.po | 7467 +++++++++++++ po/lt/kio5.po | 8083 ++++++++++++++ po/lv/kio5.po | 8636 +++++++++++++++ po/mai/kio5.po | 8019 ++++++++++++++ po/mk/kio5.po | 8987 ++++++++++++++++ po/ml/kio5.po | 7854 ++++++++++++++ po/mr/kio5.po | 7544 +++++++++++++ po/ms/kio5.po | 9102 ++++++++++++++++ po/nb/kio5.po | 7983 ++++++++++++++ po/nds/kio5.po | 8765 +++++++++++++++ po/ne/kio5.po | 9193 ++++++++++++++++ .../kcookiejar5/man-kcookiejar5.8.docbook | 225 + po/nl/docs/kioslave5/data/index.docbook | 69 + po/nl/docs/kioslave5/file/index.docbook | 31 + po/nl/docs/kioslave5/ftp/index.docbook | 49 + .../help/documentationnotfound/index.docbook | 75 + po/nl/docs/kioslave5/help/index.docbook | 27 + po/nl/docs/kioslave5/http/index.docbook | 52 + po/nl/docs/kioslave5/mailto/index.docbook | 130 + po/nl/docs/kioslave5/telnet/index.docbook | 27 + po/nl/docs/kioslave5/webdav/index.docbook | 85 + po/nl/kio5.po | 8257 ++++++++++++++ po/nn/kio5.po | 7817 ++++++++++++++ po/oc/kio5.po | 7392 +++++++++++++ po/pa/kio5.po | 7837 ++++++++++++++ po/pl/kio5.po | 8535 +++++++++++++++ po/pt/kio5.po | 8165 ++++++++++++++ .../kcookiejar5/man-kcookiejar5.8.docbook | 225 + po/pt_BR/docs/kioslave5/data/index.docbook | 83 + po/pt_BR/docs/kioslave5/file/index.docbook | 45 + po/pt_BR/docs/kioslave5/ftp/index.docbook | 63 + .../help/documentationnotfound/index.docbook | 103 + po/pt_BR/docs/kioslave5/help/index.docbook | 41 + po/pt_BR/docs/kioslave5/http/index.docbook | 66 + po/pt_BR/docs/kioslave5/mailto/index.docbook | 144 + po/pt_BR/docs/kioslave5/telnet/index.docbook | 41 + po/pt_BR/docs/kioslave5/webdav/index.docbook | 99 + po/pt_BR/kio5.po | 8139 ++++++++++++++ po/ro/kio5.po | 8878 ++++++++++++++++ po/ru/docs/kioslave5/data/index.docbook | 83 + po/ru/docs/kioslave5/file/index.docbook | 47 + po/ru/docs/kioslave5/ftp/index.docbook | 59 + po/ru/docs/kioslave5/help/index.docbook | 41 + po/ru/docs/kioslave5/telnet/index.docbook | 41 + po/ru/docs/kioslave5/webdav/index.docbook | 103 + po/ru/kio5.po | 8738 +++++++++++++++ po/se/kio5.po | 7642 +++++++++++++ po/sk/kio5.po | 8074 ++++++++++++++ po/sl/kio5.po | 9282 ++++++++++++++++ po/sq/kio5.po | 7300 +++++++++++++ po/sr/docs/kioslave5/data/index.docbook | 83 + po/sr/docs/kioslave5/file/index.docbook | 51 + po/sr/docs/kioslave5/ftp/index.docbook | 63 + .../help/documentationnotfound/index.docbook | 109 + po/sr/docs/kioslave5/help/index.docbook | 51 + po/sr/docs/kioslave5/http/index.docbook | 66 + po/sr/docs/kioslave5/mailto/index.docbook | 160 + po/sr/docs/kioslave5/telnet/index.docbook | 47 + po/sr/docs/kioslave5/webdav/index.docbook | 89 + po/sr/kio5.po | 8142 ++++++++++++++ po/sr@ijekavian/kio5.po | 8146 ++++++++++++++ po/sr@ijekavianlatin/kio5.po | 8153 ++++++++++++++ po/sr@latin/docs/kioslave5/data/index.docbook | 83 + po/sr@latin/docs/kioslave5/file/index.docbook | 51 + po/sr@latin/docs/kioslave5/ftp/index.docbook | 63 + .../help/documentationnotfound/index.docbook | 109 + po/sr@latin/docs/kioslave5/help/index.docbook | 51 + po/sr@latin/docs/kioslave5/http/index.docbook | 66 + .../docs/kioslave5/mailto/index.docbook | 160 + .../docs/kioslave5/telnet/index.docbook | 47 + .../docs/kioslave5/webdav/index.docbook | 89 + po/sr@latin/kio5.po | 8148 ++++++++++++++ .../kcookiejar5/man-kcookiejar5.8.docbook | 225 + po/sv/docs/kioslave5/data/index.docbook | 83 + po/sv/docs/kioslave5/file/index.docbook | 45 + po/sv/docs/kioslave5/ftp/index.docbook | 63 + .../help/documentationnotfound/index.docbook | 89 + po/sv/docs/kioslave5/help/index.docbook | 41 + po/sv/docs/kioslave5/http/index.docbook | 64 + po/sv/docs/kioslave5/mailto/index.docbook | 144 + po/sv/docs/kioslave5/telnet/index.docbook | 41 + po/sv/docs/kioslave5/webdav/index.docbook | 99 + po/sv/kio5.po | 8797 +++++++++++++++ po/ta/kio5.po | 9128 ++++++++++++++++ po/tg/kio5.po | 9345 ++++++++++++++++ po/th/kio5.po | 8733 +++++++++++++++ po/tr/kio5.po | 8124 ++++++++++++++ po/ug/kio5.po | 8126 ++++++++++++++ .../kcookiejar5/man-kcookiejar5.8.docbook | 225 + po/uk/docs/kioslave5/data/index.docbook | 83 + po/uk/docs/kioslave5/file/index.docbook | 45 + po/uk/docs/kioslave5/ftp/index.docbook | 63 + .../help/documentationnotfound/index.docbook | 89 + po/uk/docs/kioslave5/help/index.docbook | 41 + po/uk/docs/kioslave5/http/index.docbook | 66 + po/uk/docs/kioslave5/mailto/index.docbook | 158 + po/uk/docs/kioslave5/telnet/index.docbook | 41 + po/uk/docs/kioslave5/webdav/index.docbook | 99 + po/uk/kio5.po | 8122 ++++++++++++++ po/uz/kio5.po | 8161 ++++++++++++++ po/uz@cyrillic/kio5.po | 8130 ++++++++++++++ po/vi/kio5.po | 9282 ++++++++++++++++ po/wa/kio5.po | 8526 +++++++++++++++ po/xh/kio5.po | 9011 ++++++++++++++++ po/zh_CN/kio5.po | 8343 +++++++++++++++ po/zh_TW/kio5.po | 8493 +++++++++++++++ src/CMakeLists.txt | 24 + src/Messages.sh | 11 + src/core/CMakeLists.txt | 270 + src/core/ConfigureChecks.cmake | 45 + src/core/accept-languages.codes | 6 + src/core/authinfo.cpp | 481 + src/core/authinfo.h | 382 + src/core/chmodjob.cpp | 288 + src/core/chmodjob.h | 87 + src/core/commands_p.h | 75 + src/core/config-kiocore.h.cmake | 11 + src/core/config-kmountpoint.h.cmake | 10 + src/core/connection.cpp | 229 + src/core/connection_p.h | 169 + src/core/connectionbackend.cpp | 338 + src/core/connectionbackend_p.h | 85 + src/core/connectionserver.cpp | 112 + src/core/connectionserver.h | 78 + src/core/copyjob.cpp | 2223 ++++ src/core/copyjob.h | 428 + src/core/dataprotocol.cpp | 326 + src/core/dataprotocol_p.h | 84 + src/core/dataslave.cpp | 194 + src/core/dataslave_p.h | 127 + src/core/davjob.cpp | 164 + src/core/davjob.h | 124 + src/core/deletejob.cpp | 507 + src/core/deletejob.h | 125 + src/core/desktopexecparser.cpp | 446 + src/core/desktopexecparser.h | 123 + src/core/directorysizejob.cpp | 206 + src/core/directorysizejob.h | 98 + src/core/emptytrashjob.cpp | 67 + src/core/emptytrashjob.h | 64 + src/core/faviconscache.cpp | 195 + src/core/faviconscache_p.h | 73 + src/core/filecopyjob.cpp | 583 + src/core/filecopyjob.h | 155 + src/core/filejob.cpp | 226 + src/core/filejob.h | 162 + src/core/filesystemfreespacejob.cpp | 85 + src/core/filesystemfreespacejob.h | 73 + src/core/forwardingslavebase.cpp | 507 + src/core/forwardingslavebase.h | 192 + src/core/global.cpp | 396 + src/core/global.h | 331 + src/core/hostinfo.cpp | 400 + src/core/hostinfo.h | 57 + src/core/http_slave_defaults.h | 48 + src/core/httpmethod_p.h | 37 + src/core/idleslave.cpp | 145 + src/core/idleslave.h | 71 + src/core/ioslave_defaults.h | 54 + src/core/job.cpp | 318 + src/core/job.h | 78 + src/core/job_base.h | 307 + src/core/job_error.cpp | 1108 ++ src/core/job_p.h | 345 + src/core/jobclasses.h | 37 + src/core/jobtracker.cpp | 36 + src/core/jobtracker.h | 41 + src/core/jobuidelegateextension.cpp | 53 + src/core/jobuidelegateextension.h | 290 + src/core/jobuidelegatefactory.cpp | 49 + src/core/jobuidelegatefactory.h | 79 + src/core/kacl.cpp | 718 ++ src/core/kacl.h | 203 + src/core/kcoredirlister.cpp | 2875 +++++ src/core/kcoredirlister.h | 630 ++ src/core/kcoredirlister_p.h | 586 + src/core/kdirnotify.cpp | 88 + src/core/kdirnotify.h | 123 + src/core/kdiskfreespaceinfo.cpp | 135 + src/core/kdiskfreespaceinfo.h | 131 + src/core/kfileitem.cpp | 1614 +++ src/core/kfileitem.h | 576 + src/core/kfileitemlistproperties.cpp | 207 + src/core/kfileitemlistproperties.h | 140 + src/core/kiocoredebug.cpp | 18 + src/core/kiocoredebug.h | 23 + src/core/kioglobal_p.h | 130 + src/core/kioglobal_p_unix.cpp | 43 + src/core/kioglobal_p_win.cpp | 108 + src/core/klocalsocket.cpp | 227 + src/core/klocalsocket.h | 335 + src/core/klocalsocket_p.h | 78 + src/core/klocalsocket_unix.cpp | 424 + src/core/klocalsocket_win.cpp | 53 + src/core/kmountpoint.cpp | 563 + src/core/kmountpoint.h | 163 + src/core/knfsshare.cpp | 231 + src/core/knfsshare.h | 85 + src/core/kpasswdserverclient.cpp | 131 + src/core/kpasswdserverclient_p.h | 96 + src/core/kpasswdserverloop.cpp | 77 + src/core/kpasswdserverloop_p.h | 61 + src/core/kprotocolinfo.cpp | 418 + src/core/kprotocolinfo.h | 333 + src/core/kprotocolinfo_p.h | 74 + src/core/kprotocolinfofactory.cpp | 138 + src/core/kprotocolinfofactory_p.h | 80 + src/core/kprotocolmanager.cpp | 1329 +++ src/core/kprotocolmanager.h | 680 ++ src/core/krecentdocument.cpp | 175 + src/core/krecentdocument.h | 118 + src/core/kremoteencoding.cpp | 129 + src/core/kremoteencoding.h | 120 + src/core/ksambashare.cpp | 510 + src/core/ksambashare.h | 136 + src/core/ksambashare_p.h | 75 + src/core/ksambasharedata.cpp | 171 + src/core/ksambasharedata.h | 189 + src/core/ksambasharedata_p.h | 49 + src/core/kssl/ksslsettings.cpp | 244 + src/core/kssl/ksslsettings.h | 162 + src/core/ksslcertificatemanager.cpp | 500 + src/core/ksslcertificatemanager.h | 94 + src/core/ksslcertificatemanager_p.h | 98 + src/core/kssld_dbusmetatypes.h | 106 + src/core/kssld_interface.h | 101 + src/core/ktcpsocket.cpp | 1088 ++ src/core/ktcpsocket.h | 425 + src/core/ktcpsocket_p.h | 41 + src/core/kurlauthorized.cpp | 53 + src/core/kurlauthorized.h | 137 + src/core/listjob.cpp | 299 + src/core/listjob.h | 146 + src/core/metadata.cpp | 20 + src/core/metadata.h | 132 + src/core/mimetypejob.cpp | 99 + src/core/mimetypejob.h | 68 + src/core/mkdirjob.cpp | 126 + src/core/mkdirjob.h | 85 + src/core/mkpathjob.cpp | 146 + src/core/mkpathjob.h | 82 + src/core/multigetjob.cpp | 249 + src/core/multigetjob.h | 103 + src/core/org.kde.KDirNotify.xml | 34 + src/core/org.kde.KPasswdServer.xml | 61 + src/core/org.kde.KSlaveLauncher.xml | 24 + src/core/restorejob.cpp | 116 + src/core/restorejob.h | 83 + src/core/scheduler.cpp | 1306 +++ src/core/scheduler.h | 298 + src/core/scheduler_p.h | 187 + src/core/sessiondata.cpp | 142 + src/core/sessiondata_p.h | 53 + src/core/simplejob.cpp | 418 + src/core/simplejob.h | 279 + src/core/slave.cpp | 572 + src/core/slave.h | 249 + src/core/slavebase.cpp | 1469 +++ src/core/slavebase.h | 964 ++ src/core/slaveconfig.cpp | 231 + src/core/slaveconfig.h | 109 + src/core/slaveinterface.cpp | 437 + src/core/slaveinterface.h | 191 + src/core/slaveinterface_p.h | 66 + src/core/specialjob.cpp | 55 + src/core/specialjob.h | 75 + src/core/statjob.cpp | 215 + src/core/statjob.h | 226 + src/core/storedtransferjob.cpp | 467 + src/core/storedtransferjob.h | 147 + src/core/tcpslavebase.cpp | 1000 ++ src/core/tcpslavebase.h | 228 + src/core/transferjob.cpp | 484 + src/core/transferjob.h | 317 + src/core/udsentry.cpp | 289 + src/core/udsentry.h | 327 + src/core/usernotificationhandler.cpp | 113 + src/core/usernotificationhandler_p.h | 73 + src/filewidgets/CMakeLists.txt | 99 + src/filewidgets/Mainpage.dox | 36 + src/filewidgets/config-kiofilewidgets.h.cmake | 4 + src/filewidgets/defaults-kfile.h | 49 + src/filewidgets/defaultviewadapter.cpp | 68 + src/filewidgets/defaultviewadapter_p.h | 51 + src/filewidgets/kabstractviewadapter.h | 85 + src/filewidgets/kdiroperator.cpp | 2632 +++++ src/filewidgets/kdiroperator.h | 932 ++ src/filewidgets/kdiroperatordetailview.cpp | 187 + src/filewidgets/kdiroperatordetailview_p.h | 63 + src/filewidgets/kdirsortfilterproxymodel.cpp | 291 + src/filewidgets/kdirsortfilterproxymodel.h | 102 + src/filewidgets/kencodingfiledialog.cpp | 285 + src/filewidgets/kencodingfiledialog.h | 309 + src/filewidgets/kfilebookmarkhandler.cpp | 79 + src/filewidgets/kfilebookmarkhandler_p.h | 69 + src/filewidgets/kfilecopytomenu.cpp | 268 + src/filewidgets/kfilecopytomenu.h | 89 + src/filewidgets/kfilecopytomenu_p.h | 85 + src/filewidgets/kfilefiltercombo.cpp | 228 + src/filewidgets/kfilefiltercombo.h | 119 + src/filewidgets/kfilemetapreview.cpp | 199 + src/filewidgets/kfilemetapreview_p.h | 54 + src/filewidgets/kfileplaceeditdialog.cpp | 214 + src/filewidgets/kfileplaceeditdialog.h | 147 + src/filewidgets/kfileplacesitem.cpp | 323 + src/filewidgets/kfileplacesitem_p.h | 99 + src/filewidgets/kfileplacesmodel.cpp | 903 ++ src/filewidgets/kfileplacesmodel.h | 149 + src/filewidgets/kfileplacesview.cpp | 1206 +++ src/filewidgets/kfileplacesview.h | 113 + src/filewidgets/kfileplacesview_p.h | 109 + src/filewidgets/kfilepreviewgenerator.cpp | 1287 +++ src/filewidgets/kfilepreviewgenerator.h | 140 + src/filewidgets/kfilewidget.cpp | 2843 +++++ src/filewidgets/kfilewidget.h | 574 + src/filewidgets/kimagefilepreview.cpp | 281 + src/filewidgets/kimagefilepreview.h | 84 + src/filewidgets/knameandurlinputdialog.cpp | 151 + src/filewidgets/knameandurlinputdialog.h | 85 + src/filewidgets/knewfilemenu.cpp | 1267 +++ src/filewidgets/knewfilemenu.h | 187 + src/filewidgets/kpreviewwidgetbase.cpp | 37 + src/filewidgets/kpreviewwidgetbase.h | 86 + src/filewidgets/krecentdirs.cpp | 83 + src/filewidgets/krecentdirs.h | 77 + .../kstatusbarofflineindicator.cpp | 81 + src/filewidgets/kstatusbarofflineindicator.h | 59 + src/filewidgets/kurlnavigator.cpp | 1276 +++ src/filewidgets/kurlnavigator.h | 470 + src/filewidgets/kurlnavigatorbutton.cpp | 696 ++ src/filewidgets/kurlnavigatorbutton_p.h | 203 + src/filewidgets/kurlnavigatorbuttonbase.cpp | 151 + src/filewidgets/kurlnavigatorbuttonbase_p.h | 91 + .../kurlnavigatordropdownbutton.cpp | 85 + .../kurlnavigatordropdownbutton_p.h | 53 + src/filewidgets/kurlnavigatormenu.cpp | 73 + src/filewidgets/kurlnavigatormenu_p.h | 64 + .../kurlnavigatorplacesselector.cpp | 237 + .../kurlnavigatorplacesselector_p.h | 114 + .../kurlnavigatorprotocolcombo.cpp | 234 + .../kurlnavigatorprotocolcombo_p.h | 87 + src/filewidgets/kurlnavigatortogglebutton.cpp | 109 + src/filewidgets/kurlnavigatortogglebutton_p.h | 61 + src/gui/CMakeLists.txt | 49 + src/gui/faviconrequestjob.cpp | 215 + src/gui/faviconrequestjob.h | 128 + src/ioslaves/CMakeLists.txt | 9 + src/ioslaves/DEBUG.howto | 4 + src/ioslaves/Mainpage.dox | 36 + src/ioslaves/file/CMakeLists.txt | 35 + src/ioslaves/file/ConfigureChecks.cmake | 15 + .../file/config-kioslave-file.h.cmake | 12 + src/ioslaves/file/file.cpp | 1360 +++ src/ioslaves/file/file.h | 106 + src/ioslaves/file/file.json | 34 + src/ioslaves/file/file_unix.cpp | 636 ++ src/ioslaves/file/file_win.cpp | 376 + src/ioslaves/ftp/CMakeLists.txt | 20 + src/ioslaves/ftp/ConfigureChecks.cmake | 9 + src/ioslaves/ftp/config-kioslave-ftp.h.cmake | 3 + src/ioslaves/ftp/ftp.cpp | 2697 +++++ src/ioslaves/ftp/ftp.h | 447 + src/ioslaves/ftp/ftp.json | 34 + src/ioslaves/help/CMakeLists.txt | 87 + src/ioslaves/help/ConfigureChecks.cmake | 7 + src/ioslaves/help/config-help.h.cmake | 7 + src/ioslaves/help/ghelp.json | 14 + src/ioslaves/help/help.json | 15 + src/ioslaves/help/kio_help.cpp | 413 + src/ioslaves/help/kio_help.h | 59 + src/ioslaves/help/main.cpp | 65 + src/ioslaves/help/main_ghelp.cpp | 64 + src/ioslaves/help/xslt_help.cpp | 99 + src/ioslaves/help/xslt_help.h | 14 + src/ioslaves/http/CMakeLists.txt | 94 + src/ioslaves/http/ConfigureChecks.cmake | 3 + src/ioslaves/http/README.http_cache_cleaner | 20 + src/ioslaves/http/README.webdav | 184 + src/ioslaves/http/RFCs | 7 + src/ioslaves/http/THOUGHTS | 28 + src/ioslaves/http/TODO | 35 + src/ioslaves/http/config-gssapi.h.cmake | 5 + .../http/config-kioslave-http.h.cmake | 2 + src/ioslaves/http/http.cpp | 5647 ++++++++++ src/ioslaves/http/http.h | 615 ++ src/ioslaves/http/http.json | 92 + src/ioslaves/http/http_cache_cleaner.cpp | 850 ++ src/ioslaves/http/http_cache_cleaner.desktop | 104 + src/ioslaves/http/httpauthentication.cpp | 926 ++ src/ioslaves/http/httpauthentication.h | 292 + src/ioslaves/http/httpfilter.cpp | 206 + src/ioslaves/http/httpfilter.h | 104 + src/ioslaves/http/kcookiejar/CMakeLists.txt | 49 + src/ioslaves/http/kcookiejar/domain_info | 2 + src/ioslaves/http/kcookiejar/kcookiejar.cpp | 1604 +++ src/ioslaves/http/kcookiejar/kcookiejar.h | 472 + src/ioslaves/http/kcookiejar/kcookiejar.json | 72 + .../http/kcookiejar/kcookiejar_include.h | 9 + .../http/kcookiejar/kcookieserver.cpp | 599 ++ src/ioslaves/http/kcookiejar/kcookieserver.h | 115 + src/ioslaves/http/kcookiejar/kcookiewin.cpp | 387 + src/ioslaves/http/kcookiejar/kcookiewin.h | 83 + src/ioslaves/http/kcookiejar/main.cpp | 74 + .../http/kcookiejar/netscape_cookie_spec.html | 331 + .../kcookiejar/org.kde.kcookiejar5.service.in | 4 + src/ioslaves/http/kcookiejar/specifications | 3 + src/ioslaves/http/parsinghelpers.cpp | 601 ++ src/ioslaves/http/parsinghelpers.h | 90 + src/ioslaves/http/shoutcast-icecast.txt | 605 ++ src/ioslaves/protocols/CMakeLists.txt | 12 + src/ioslaves/protocols/data.protocol | 57 + src/ioslaves/protocols/mms.protocol | 60 + src/ioslaves/protocols/mmst.protocol | 14 + src/ioslaves/protocols/mmsu.protocol | 14 + src/ioslaves/protocols/pnm.protocol | 14 + src/ioslaves/protocols/rtsp.protocol | 15 + src/ioslaves/protocols/rtspt.protocol | 14 + src/ioslaves/protocols/rtspu.protocol | 14 + src/ioslaves/telnet/CMakeLists.txt | 13 + src/ioslaves/telnet/ktelnetservice.cpp | 102 + src/ioslaves/telnet/ktelnetservice5.desktop | 56 + src/ioslaves/trash/CMakeLists.txt | 76 + src/ioslaves/trash/DESIGN | 55 + src/ioslaves/trash/discspaceutil.cpp | 95 + src/ioslaves/trash/discspaceutil.h | 73 + src/ioslaves/trash/kcmtrash.cpp | 312 + src/ioslaves/trash/kcmtrash.desktop | 391 + src/ioslaves/trash/kcmtrash.h | 82 + src/ioslaves/trash/kinterprocesslock.cpp | 93 + src/ioslaves/trash/kinterprocesslock.h | 118 + src/ioslaves/trash/kio_trash.cpp | 640 ++ src/ioslaves/trash/kio_trash.h | 82 + src/ioslaves/trash/kio_trash_win.cpp | 454 + src/ioslaves/trash/kio_trash_win.h | 78 + src/ioslaves/trash/ktrash.cpp | 81 + src/ioslaves/trash/tests/CMakeLists.txt | 30 + src/ioslaves/trash/tests/lockingtest.cpp | 55 + src/ioslaves/trash/tests/testtrash.cpp | 1243 +++ src/ioslaves/trash/tests/testtrash.h | 131 + src/ioslaves/trash/trash.json | 133 + src/ioslaves/trash/trashimpl.cpp | 1277 +++ src/ioslaves/trash/trashimpl.h | 206 + src/ioslaves/trash/trashsizecache.cpp | 158 + src/ioslaves/trash/trashsizecache.h | 74 + src/kcms/CMakeLists.txt | 2 + src/kcms/kio/CMakeLists.txt | 55 + src/kcms/kio/UA-DESKTOP-FILE-HOWTO | 135 + src/kcms/kio/cache.cpp | 130 + src/kcms/kio/cache.desktop | 177 + src/kcms/kio/cache.h | 52 + src/kcms/kio/cache.ui | 221 + src/kcms/kio/cookies.desktop | 176 + src/kcms/kio/kcookiesmain.cpp | 97 + src/kcms/kio/kcookiesmain.h | 40 + src/kcms/kio/kcookiesmanagement.cpp | 428 + src/kcms/kio/kcookiesmanagement.h | 93 + src/kcms/kio/kcookiesmanagement.ui | 260 + src/kcms/kio/kcookiespolicies.cpp | 457 + src/kcms/kio/kcookiespolicies.h | 78 + src/kcms/kio/kcookiespolicies.ui | 236 + src/kcms/kio/kcookiespolicyselectiondlg.cpp | 140 + src/kcms/kio/kcookiespolicyselectiondlg.h | 90 + src/kcms/kio/kcookiespolicyselectiondlg.ui | 117 + src/kcms/kio/kio_ftprc.kcfg | 22 + src/kcms/kio/kio_ftprc.kcfgc | 5 + src/kcms/kio/kioslave.kcfg | 51 + src/kcms/kio/kioslave.kcfgc | 5 + src/kcms/kio/kproxydlg.cpp | 569 + src/kcms/kio/kproxydlg.h | 72 + src/kcms/kio/kproxydlg.ui | 949 ++ src/kcms/kio/ksaveioconfig.cpp | 240 + src/kcms/kio/ksaveioconfig.h | 82 + src/kcms/kio/main.cpp | 46 + src/kcms/kio/netpref.cpp | 175 + src/kcms/kio/netpref.desktop | 217 + src/kcms/kio/netpref.h | 41 + src/kcms/kio/proxy.desktop | 223 + src/kcms/kio/smb.desktop | 215 + src/kcms/kio/smbrodlg.cpp | 196 + src/kcms/kio/smbrodlg.h | 55 + src/kcms/kio/uasprovider.desktop | 106 + src/kcms/kio/uasproviders/CMakeLists.txt | 3 + src/kcms/kio/uasproviders/android10.desktop | 73 + .../uasproviders/chrome10onwinnt51.desktop | 74 + .../uasproviders/chrome22oncurrent.desktop | 46 + .../uasproviders/chrome23oncurrent.desktop | 46 + .../uasproviders/chrome24oncurrent.desktop | 46 + .../uasproviders/chrome50oncurrent.desktop | 68 + .../uasproviders/firefox15oncurrent.desktop | 46 + .../uasproviders/firefox16oncurrent.desktop | 46 + .../uasproviders/firefox20oncurrent.desktop | 85 + .../uasproviders/firefox30oncurrent.desktop | 72 + .../uasproviders/firefox36oncurrent.desktop | 68 + src/kcms/kio/uasproviders/googlebot.desktop | 89 + .../kio/uasproviders/ie401onwinnt4.desktop | 94 + src/kcms/kio/uasproviders/ie50onppc.desktop | 94 + .../kio/uasproviders/ie55onwinnt5.desktop | 94 + .../kio/uasproviders/ie60oncurrent.desktop | 89 + .../kio/uasproviders/ie60onwinnt51.desktop | 94 + .../kio/uasproviders/ie70onwinnt51.desktop | 73 + .../kio/uasproviders/ie80onwinnt60.desktop | 47 + .../kio/uasproviders/ie90onwinnt71.desktop | 47 + .../kio/uasproviders/lynxoncurrent.desktop | 91 + .../kio/uasproviders/nn301oncurrent.desktop | 91 + .../kio/uasproviders/nn475oncurrent.desktop | 91 + .../kio/uasproviders/nn475onwin95.desktop | 94 + .../kio/uasproviders/ns71oncurrent.desktop | 89 + .../kio/uasproviders/ns71onwinnt51.desktop | 91 + .../kio/uasproviders/op1162oncurrent.desktop | 46 + .../kio/uasproviders/op1202oncurrent.desktop | 46 + .../kio/uasproviders/op403onwinnt4.desktop | 93 + .../kio/uasproviders/op85oncurrent.desktop | 87 + .../kio/uasproviders/op90oncurrent.desktop | 85 + .../kio/uasproviders/op962oncurrent.desktop | 72 + src/kcms/kio/uasproviders/safari20.desktop | 90 + .../kio/uasproviders/safari30oniphone.desktop | 74 + src/kcms/kio/uasproviders/safari32.desktop | 74 + src/kcms/kio/uasproviders/safari40.desktop | 70 + src/kcms/kio/uasproviders/safari517.desktop | 48 + src/kcms/kio/uasproviders/safari60.desktop | 48 + .../kio/uasproviders/w3moncurrent.desktop | 91 + .../kio/uasproviders/wgetoncurrent.desktop | 71 + src/kcms/kio/useragent.desktop | 216 + src/kcms/kio/useragentdlg.cpp | 390 + src/kcms/kio/useragentdlg.h | 89 + src/kcms/kio/useragentdlg.ui | 313 + src/kcms/kio/useragentinfo.cpp | 190 + src/kcms/kio/useragentinfo.h | 58 + src/kcms/kio/useragentselectordlg.cpp | 161 + src/kcms/kio/useragentselectordlg.h | 56 + src/kcms/kio/useragentselectordlg.ui | 128 + src/kcms/webshortcuts/CMakeLists.txt | 13 + src/kcms/webshortcuts/main.cpp | 126 + src/kcms/webshortcuts/main.h | 45 + src/kcms/webshortcuts/webshortcuts.desktop | 184 + src/kiod/CMakeLists.txt | 20 + src/kiod/kiod_main.cpp | 118 + src/kiod/org.kde.kiod5.service.in | 4 + src/kioexec/CMakeLists.txt | 21 + src/kioexec/README | 26 + src/kioexec/config-kioexec.h.cmake | 2 + src/kioexec/main.cpp | 301 + src/kioexec/main.h | 74 + src/kioslave/CMakeLists.txt | 8 + src/kioslave/kioslave.cpp | 131 + src/kntlm/CMakeLists.txt | 22 + src/kntlm/des.cpp | 547 + src/kntlm/des.h | 41 + src/kntlm/kntlm.cpp | 417 + src/kntlm/kntlm.h | 222 + src/kpac/CMakeLists.txt | 76 + src/kpac/ConfigureChecks.cmake | 22 + src/kpac/README | 9 + src/kpac/README.wpad | 73 + src/kpac/TODO | 1 + src/kpac/config-kpac.h.cmake | 12 + src/kpac/dhcp.h | 76 + src/kpac/discovery.cpp | 147 + src/kpac/discovery.h | 51 + src/kpac/downloader.cpp | 101 + src/kpac/downloader.h | 76 + src/kpac/kpac_dhcp_helper.c | 294 + src/kpac/kpactest.pac | 177 + src/kpac/kpactest2.pac | 64 + src/kpac/proxyscout.cpp | 363 + src/kpac/proxyscout.h | 85 + src/kpac/proxyscout.json | 76 + src/kpac/proxyscout.notifyrc | 350 + src/kpac/script.cpp | 769 ++ src/kpac/script.h | 58 + src/kpasswdserver/CMakeLists.txt | 36 + src/kpasswdserver/DESIGN | 74 + src/kpasswdserver/autotests/CMakeLists.txt | 13 + .../autotests/kpasswdservertest.cpp | 566 + src/kpasswdserver/kiod_kpasswdserver.cpp | 32 + src/kpasswdserver/kpasswdserver.cpp | 1119 ++ src/kpasswdserver/kpasswdserver.h | 137 + src/kpasswdserver/kpasswdserver.json | 75 + .../org.kde.kpasswdserver.service.in | 4 + src/kssld/CMakeLists.txt | 19 + src/kssld/kssld.cpp | 270 + src/kssld/kssld.h | 53 + src/kssld/kssld.json | 74 + src/kssld/kssld_adaptor.h | 73 + src/kssld/org.kde.kssld5.service.in | 4 + src/new_file_templates/Directory.desktop | 165 + src/new_file_templates/HTMLFile.desktop | 164 + src/new_file_templates/HTMLFile.html | 8 + src/new_file_templates/Program.desktop | 5 + src/new_file_templates/TextFile.desktop | 163 + src/new_file_templates/TextFile.txt | 1 + src/new_file_templates/URL.desktop | 3 + src/new_file_templates/linkPath.desktop | 122 + src/new_file_templates/linkProgram.desktop | 160 + src/new_file_templates/linkURL.desktop | 165 + src/new_file_templates/templates.qrc | 18 + src/protocoltojson/CMakeLists.txt | 13 + src/protocoltojson/main.cpp | 184 + src/urifilters/CMakeLists.txt | 4 + src/urifilters/fixhost/CMakeLists.txt | 12 + src/urifilters/fixhost/fixhosturifilter.cpp | 95 + .../fixhost/fixhosturifilter.desktop | 88 + src/urifilters/fixhost/fixhosturifilter.h | 45 + src/urifilters/ikws/CMakeLists.txt | 41 + src/urifilters/ikws/ikwsopts.cpp | 456 + src/urifilters/ikws/ikwsopts.h | 68 + src/urifilters/ikws/ikwsopts_p.h | 91 + src/urifilters/ikws/ikwsopts_ui.ui | 285 + src/urifilters/ikws/kuriikwsfilter.cpp | 171 + src/urifilters/ikws/kuriikwsfilter.desktop | 94 + src/urifilters/ikws/kuriikwsfilter.h | 43 + src/urifilters/ikws/kuriikwsfiltereng.cpp | 442 + src/urifilters/ikws/kuriikwsfiltereng.h | 76 + src/urifilters/ikws/kurisearchfilter.cpp | 95 + src/urifilters/ikws/kurisearchfilter.desktop | 94 + src/urifilters/ikws/kurisearchfilter.h | 42 + src/urifilters/ikws/searchprovider.cpp | 140 + src/urifilters/ikws/searchprovider.desktop | 105 + src/urifilters/ikws/searchprovider.h | 53 + src/urifilters/ikws/searchproviderdlg.cpp | 175 + src/urifilters/ikws/searchproviderdlg.h | 54 + src/urifilters/ikws/searchproviderdlg_ui.ui | 190 + .../ikws/searchproviders/7digital.desktop | 143 + .../ikws/searchproviders/CMakeLists.txt | 8 + .../ikws/searchproviders/acronym.desktop | 145 + .../ikws/searchproviders/amazon.desktop | 142 + .../ikws/searchproviders/amazon_mp3.desktop | 144 + .../ikws/searchproviders/amg.desktop | 145 + .../ikws/searchproviders/archpkg.desktop | 76 + .../ikws/searchproviders/backports.desktop | 141 + .../ikws/searchproviders/baidu.desktop | 140 + .../ikws/searchproviders/beolingus.desktop | 142 + .../ikws/searchproviders/bing.desktop | 140 + .../ikws/searchproviders/blip.desktop | 128 + .../ikws/searchproviders/bugft.desktop | 147 + .../ikws/searchproviders/bugno.desktop | 164 + .../ikws/searchproviders/call.desktop | 155 + .../ikws/searchproviders/cia.desktop | 142 + .../ikws/searchproviders/citeseer.desktop | 148 + .../ikws/searchproviders/cpan.desktop | 156 + .../ikws/searchproviders/ctan.desktop | 147 + .../ikws/searchproviders/ctan_cat.desktop | 176 + .../ikws/searchproviders/dbug.desktop | 179 + .../ikws/searchproviders/de2en.desktop | 182 + .../ikws/searchproviders/de2fr.desktop | 180 + .../ikws/searchproviders/deb.desktop | 180 + .../ikws/searchproviders/dictfr.desktop | 147 + .../ikws/searchproviders/dmoz.desktop | 184 + .../ikws/searchproviders/docbook.desktop | 185 + .../ikws/searchproviders/doi.desktop | 182 + .../ikws/searchproviders/duckduckgo.desktop | 127 + .../searchproviders/duckduckgo_info.desktop | 127 + .../duckduckgo_shopping.desktop | 128 + .../ikws/searchproviders/ecosia.desktop | 142 + .../ikws/searchproviders/en2de.desktop | 182 + .../ikws/searchproviders/en2es.desktop | 183 + .../ikws/searchproviders/en2fr.desktop | 183 + .../ikws/searchproviders/en2it.desktop | 183 + .../ikws/searchproviders/es2en.desktop | 183 + .../ikws/searchproviders/ethicle.desktop | 146 + .../ikws/searchproviders/facebook.desktop | 129 + .../ikws/searchproviders/feedster.desktop | 149 + .../ikws/searchproviders/flickr.desktop | 144 + .../ikws/searchproviders/flickrcc.desktop | 127 + .../ikws/searchproviders/foldoc.desktop | 147 + .../ikws/searchproviders/fr2de.desktop | 180 + .../ikws/searchproviders/fr2en.desktop | 183 + .../ikws/searchproviders/freecode.desktop | 111 + .../ikws/searchproviders/freedb.desktop | 184 + .../ikws/searchproviders/fsd.desktop | 176 + .../ikws/searchproviders/github.desktop | 129 + .../ikws/searchproviders/gitorious.desktop | 129 + .../ikws/searchproviders/google.desktop | 152 + .../searchproviders/google_advanced.desktop | 151 + .../ikws/searchproviders/google_code.desktop | 127 + .../searchproviders/google_groups.desktop | 150 + .../searchproviders/google_images.desktop | 150 + .../ikws/searchproviders/google_lucky.desktop | 149 + .../ikws/searchproviders/google_maps.desktop | 126 + .../ikws/searchproviders/google_movie.desktop | 151 + .../ikws/searchproviders/google_news.desktop | 150 + .../searchproviders/google_shopping.desktop | 111 + .../ikws/searchproviders/grec.desktop | 115 + .../searchproviders/hyperdictionary.desktop | 184 + .../hyperdictionary_thesaurus.desktop | 183 + .../ikws/searchproviders/ibl.desktop | 177 + .../searchproviders/identica_groups.desktop | 129 + .../searchproviders/identica_notices.desktop | 129 + .../searchproviders/identica_people.desktop | 128 + .../ikws/searchproviders/imdb.desktop | 162 + .../ikws/searchproviders/it2en.desktop | 183 + .../ikws/searchproviders/jamendo.desktop | 130 + .../ikws/searchproviders/jeeves.desktop | 183 + .../ikws/searchproviders/kde.desktop | 180 + .../ikws/searchproviders/kde_apps.desktop | 183 + .../ikws/searchproviders/kde_forums.desktop | 129 + .../ikws/searchproviders/kde_look.desktop | 144 + .../ikws/searchproviders/kde_projects.desktop | 112 + .../ikws/searchproviders/kde_techbase.desktop | 143 + .../ikws/searchproviders/kde_userbase.desktop | 144 + .../ikws/searchproviders/leo.desktop | 183 + .../ikws/searchproviders/magnatune.desktop | 144 + .../ikws/searchproviders/metacrawler.desktop | 149 + .../ikws/searchproviders/msdn.desktop | 148 + .../searchproviders/multitran-deru.desktop | 171 + .../searchproviders/multitran-enru.desktop | 171 + .../searchproviders/multitran-esru.desktop | 171 + .../searchproviders/multitran-frru.desktop | 171 + .../searchproviders/multitran-itru.desktop | 171 + .../searchproviders/multitran-nlru.desktop | 171 + .../ikws/searchproviders/netcraft.desktop | 183 + .../ikws/searchproviders/nl-telephone.desktop | 142 + .../ikws/searchproviders/nl-teletekst.desktop | 181 + .../ikws/searchproviders/opendesktop.desktop | 142 + .../ikws/searchproviders/pgpkeys.desktop | 147 + .../ikws/searchproviders/php.desktop | 163 + .../ikws/searchproviders/python.desktop | 150 + .../ikws/searchproviders/qt.desktop | 149 + .../ikws/searchproviders/qt4.desktop | 112 + .../ikws/searchproviders/qwant.desktop | 88 + .../ikws/searchproviders/qwant_images.desktop | 89 + .../ikws/searchproviders/qwant_news.desktop | 88 + .../searchproviders/qwant_shopping.desktop | 89 + .../ikws/searchproviders/qwant_social.desktop | 88 + .../ikws/searchproviders/qwant_videos.desktop | 90 + .../ikws/searchproviders/rae.desktop | 150 + .../ikws/searchproviders/rag.desktop | 109 + .../ikws/searchproviders/rfc.desktop | 144 + .../ikws/searchproviders/rpmfind.desktop | 182 + .../ruby_application_archive.desktop | 182 + .../ikws/searchproviders/soundcloud.desktop | 65 + .../ikws/searchproviders/sourceforge.desktop | 182 + .../ikws/searchproviders/technorati.desktop | 148 + .../searchproviders/technoratitags.desktop | 179 + .../ikws/searchproviders/thesaurus.desktop | 183 + .../ikws/searchproviders/tvtome.desktop | 182 + .../searchproviders/urbandictionary.desktop | 143 + .../ikws/searchproviders/uspto.desktop | 183 + .../ikws/searchproviders/vimeo.desktop | 129 + .../ikws/searchproviders/voila.desktop | 183 + .../ikws/searchproviders/webster.desktop | 184 + .../ikws/searchproviders/wikia.desktop | 143 + .../ikws/searchproviders/wikipedia.desktop | 149 + .../ikws/searchproviders/wiktionary.desktop | 147 + .../searchproviders/wolfram_alpha.desktop | 130 + .../ikws/searchproviders/wordref.desktop | 184 + .../ikws/searchproviders/yahoo.desktop | 142 + .../ikws/searchproviders/yahoo_image.desktop | 143 + .../ikws/searchproviders/yahoo_local.desktop | 142 + .../searchproviders/yahoo_shopping.desktop | 142 + .../ikws/searchproviders/yahoo_video.desktop | 142 + .../ikws/searchproviders/youtube.desktop | 128 + src/urifilters/localdomain/CMakeLists.txt | 11 + .../localdomain/localdomainurifilter.cpp | 82 + .../localdomain/localdomainurifilter.desktop | 94 + .../localdomain/localdomainurifilter.h | 51 + src/urifilters/shorturi/CMakeLists.txt | 12 + src/urifilters/shorturi/kshorturifilter.cpp | 576 + .../shorturi/kshorturifilter.desktop | 95 + src/urifilters/shorturi/kshorturifilter.h | 108 + src/urifilters/shorturi/kshorturifilterrc | 7 + src/widgets/CMakeLists.txt | 203 + src/widgets/accessmanager.cpp | 585 + src/widgets/accessmanager.h | 364 + src/widgets/accessmanagerreply_p.cpp | 490 + src/widgets/accessmanagerreply_p.h | 107 + src/widgets/certificateparty.ui | 151 + src/widgets/checksumswidget.ui | 221 + src/widgets/clipboardupdater.cpp | 189 + src/widgets/clipboardupdater_p.h | 77 + src/widgets/config-kiowidgets.h.cmake | 12 + src/widgets/delegateanimationhandler.cpp | 442 + src/widgets/delegateanimationhandler_p.h | 173 + src/widgets/dndpopupmenuplugin.cpp | 30 + src/widgets/dndpopupmenuplugin.h | 69 + src/widgets/dropjob.cpp | 469 + src/widgets/dropjob.h | 114 + src/widgets/executablefileopendialog.cpp | 63 + src/widgets/executablefileopendialog_p.h | 47 + src/widgets/fileundomanager.cpp | 752 ++ src/widgets/fileundomanager.h | 223 + src/widgets/fileundomanager_p.h | 178 + src/widgets/imagefilter.cpp | 262 + src/widgets/imagefilter_p.h | 56 + src/widgets/images/group-grey.png | Bin 0 -> 2555 bytes src/widgets/images/group.png | Bin 0 -> 2555 bytes src/widgets/images/mask.png | Bin 0 -> 885 bytes src/widgets/images/others-grey.png | Bin 0 -> 944 bytes src/widgets/images/others.png | Bin 0 -> 944 bytes src/widgets/images/user-grey.png | Bin 0 -> 2473 bytes src/widgets/images/user.png | Bin 0 -> 2473 bytes src/widgets/images/yes.png | Bin 0 -> 726 bytes src/widgets/images/yespartial.png | Bin 0 -> 792 bytes src/widgets/jobuidelegate.cpp | 397 + src/widgets/jobuidelegate.h | 170 + src/widgets/joburlcache.cpp | 64 + src/widgets/joburlcache_p.h | 53 + src/widgets/kabstractfileitemactionplugin.cpp | 31 + src/widgets/kabstractfileitemactionplugin.h | 112 + src/widgets/kacleditwidget.cpp | 1166 ++ src/widgets/kacleditwidget.h | 54 + src/widgets/kacleditwidget.qrc | 15 + src/widgets/kacleditwidget_p.h | 219 + src/widgets/kautomount.cpp | 154 + src/widgets/kautomount.h | 114 + src/widgets/kbuildsycocaprogressdialog.cpp | 76 + src/widgets/kbuildsycocaprogressdialog.h | 51 + src/widgets/kdesktopfileactions.cpp | 394 + src/widgets/kdesktopfileactions.h | 117 + src/widgets/kdirlister.cpp | 103 + src/widgets/kdirlister.h | 106 + src/widgets/kdirmodel.cpp | 1282 +++ src/widgets/kdirmodel.h | 284 + src/widgets/kdynamicjobtracker.cpp | 135 + src/widgets/kdynamicjobtracker_p.h | 69 + src/widgets/kfile.cpp | 93 + src/widgets/kfile.h | 114 + src/widgets/kfileitemactionplugin.desktop | 49 + src/widgets/kfileitemactions.cpp | 790 ++ src/widgets/kfileitemactions.h | 193 + src/widgets/kfileitemactions_p.h | 94 + src/widgets/kfileitemdelegate.cpp | 1621 +++ src/widgets/kfileitemdelegate.h | 442 + src/widgets/kiodndpopupmenuplugin.desktop | 39 + src/widgets/konqpopupmenuplugin.desktop | 40 + src/widgets/kopenwithdialog.cpp | 1039 ++ src/widgets/kopenwithdialog.h | 144 + src/widgets/kopenwithdialog_p.h | 95 + src/widgets/koverlayiconplugin.cpp | 31 + src/widgets/koverlayiconplugin.h | 63 + src/widgets/kpropertiesdesktopadvbase.ui | 297 + src/widgets/kpropertiesdesktopbase.ui | 273 + src/widgets/kpropertiesdialog.cpp | 3867 +++++++ src/widgets/kpropertiesdialog.h | 470 + src/widgets/kpropertiesdialog_p.h | 296 + src/widgets/kpropertiesdialogplugin.desktop | 56 + src/widgets/krun.cpp | 1573 +++ src/widgets/krun.h | 627 ++ src/widgets/krun_p.h | 140 + src/widgets/krun_win.cpp | 101 + src/widgets/kshellcompletion.cpp | 320 + src/widgets/kshellcompletion.h | 69 + src/widgets/ksslcertificatebox.cpp | 79 + src/widgets/ksslcertificatebox.h | 50 + src/widgets/ksslinfodialog.cpp | 242 + src/widgets/ksslinfodialog.h | 101 + src/widgets/kurifilter.cpp | 693 ++ src/widgets/kurifilter.h | 1002 ++ src/widgets/kurifilterplugin.desktop | 54 + .../kurifiltersearchprovideractions.cpp | 125 + src/widgets/kurifiltersearchprovideractions.h | 79 + src/widgets/kurlcombobox.cpp | 451 + src/widgets/kurlcombobox.h | 205 + src/widgets/kurlcompletion.cpp | 1537 +++ src/widgets/kurlcompletion.h | 185 + src/widgets/kurlpixmapprovider.cpp | 44 + src/widgets/kurlpixmapprovider.h | 74 + src/widgets/kurlrequester.cpp | 625 ++ src/widgets/kurlrequester.h | 356 + src/widgets/kurlrequesterdialog.cpp | 143 + src/widgets/kurlrequesterdialog.h | 107 + src/widgets/openfilemanagerwindowjob.cpp | 158 + src/widgets/openfilemanagerwindowjob.h | 122 + src/widgets/openfilemanagerwindowjob_p.h | 74 + src/widgets/org.kde.kio.FileUndoManager.xml | 15 + src/widgets/org.kde.kuiserver.xml | 24 + src/widgets/paste.cpp | 357 + src/widgets/paste.h | 112 + src/widgets/pastedialog.cpp | 97 + src/widgets/pastedialog_p.h | 66 + src/widgets/pastejob.cpp | 106 + src/widgets/pastejob.h | 93 + src/widgets/pastejob_p.h | 66 + src/widgets/pixmaploader.cpp | 29 + src/widgets/pixmaploader.h | 53 + src/widgets/previewjob.cpp | 767 ++ src/widgets/previewjob.h | 302 + src/widgets/renamedialog.cpp | 669 ++ src/widgets/renamedialog.h | 131 + src/widgets/skipdialog.cpp | 91 + src/widgets/skipdialog.h | 56 + src/widgets/sslinfo.ui | 271 + src/widgets/sslui.cpp | 135 + src/widgets/sslui.h | 45 + src/widgets/thumbcreator.cpp | 45 + src/widgets/thumbcreator.h | 183 + src/widgets/thumbsequencecreator.cpp | 50 + src/widgets/thumbsequencecreator.h | 80 + tests/CMakeLists.txt | 44 + tests/getalltest.cpp | 40 + tests/kdirlistertest_gui.cpp | 165 + tests/kdirlistertest_gui.h | 142 + tests/kdirmodeltest_gui.cpp | 110 + tests/kencodingfiledialogtest_gui.cpp | 34 + tests/kfilewidgettest_gui.cpp | 36 + tests/kionetrctest.cpp | 51 + tests/kioslavetest.cpp | 541 + tests/kioslavetest.h | 113 + tests/kmountpoint_debug.cpp | 70 + tests/kopenwithtest.cpp | 68 + tests/kpropertiesdialogtest.cpp | 24 + tests/kprotocolinfo_dumper.cpp | 31 + tests/kruntest.cpp | 154 + tests/kruntest.h | 58 + tests/ksycocaupdatetest.cpp | 12 + tests/kurlnavigatortest_gui.cpp | 39 + tests/kurlrequestertest_gui.cpp | 37 + tests/listjobtest.cpp | 79 + tests/listrecursivetest.cpp | 97 + tests/previewtest.cpp | 70 + tests/previewtest.h | 29 + tests/runapplication.cpp | 47 + tests/udsentrybenchmark.cpp | 330 + 1125 files changed, 906618 insertions(+) create mode 100644 .reviewboardrc create mode 100644 CMakeLists.txt create mode 100644 COPYING.LIB create mode 100644 KF5KIOConfig.cmake.in create mode 100644 README.md create mode 100644 autotests/CMakeLists.txt create mode 100644 autotests/accessmanagertest.cpp create mode 100644 autotests/clipboardupdatertest.cpp create mode 100644 autotests/clipboardupdatertest.h create mode 100644 autotests/dataprotocoltest.cpp create mode 100644 autotests/dataprotocoltest.h create mode 100644 autotests/deletejobtest.cpp create mode 100644 autotests/deletejobtest.h create mode 100644 autotests/dropjobtest.cpp create mode 100644 autotests/fakecomputer.xml create mode 100644 autotests/favicontest.cpp create mode 100644 autotests/fileundomanagertest.cpp create mode 100644 autotests/fileundomanagertest.h create mode 100644 autotests/globaltest.cpp create mode 100644 autotests/globaltest.h create mode 100644 autotests/http/CMakeLists.txt create mode 100644 autotests/http/httpauthenticationtest.cpp create mode 100644 autotests/http/httpauthenticationtest.h create mode 100644 autotests/http/httpfiltertest.cpp create mode 100644 autotests/http/httpheaderdispositiontest.cpp create mode 100644 autotests/http/httpheaderdispositiontest.h create mode 100644 autotests/http/httpheadertokenizetest.cpp create mode 100644 autotests/http/httpheadertokenizetest.h create mode 100644 autotests/http/httpobjecttest.cpp create mode 100644 autotests/http/httpobjecttest.h create mode 100644 autotests/http_jobtest.cpp create mode 100644 autotests/httpserver_p.cpp create mode 100644 autotests/httpserver_p.h create mode 100644 autotests/jobguitest.cpp create mode 100644 autotests/jobremotetest.cpp create mode 100644 autotests/jobremotetest.h create mode 100644 autotests/jobtest.cpp create mode 100644 autotests/jobtest.h create mode 100644 autotests/kacltest.cpp create mode 100644 autotests/kacltest.h create mode 100644 autotests/kcookiejar/CMakeLists.txt create mode 100644 autotests/kcookiejar/cookie.test create mode 100644 autotests/kcookiejar/cookie_rfc.test create mode 100644 autotests/kcookiejar/cookie_saving.test create mode 100644 autotests/kcookiejar/cookie_session.test create mode 100644 autotests/kcookiejar/cookie_settings.test create mode 100644 autotests/kcookiejar/kcookiejartest.cpp create mode 100644 autotests/kdirlistertest.cpp create mode 100644 autotests/kdirlistertest.h create mode 100644 autotests/kdirmodeltest.cpp create mode 100644 autotests/kdirmodeltest.h create mode 100644 autotests/kdiroperatortest.cpp create mode 100644 autotests/kfilecopytomenutest.cpp create mode 100644 autotests/kfileitemactionstest.cpp create mode 100644 autotests/kfileitemactionstest.h create mode 100644 autotests/kfileitemtest.cpp create mode 100644 autotests/kfileitemtest.h create mode 100644 autotests/kfileplacesmodeltest.cpp create mode 100644 autotests/kfilewidgettest.cpp create mode 100644 autotests/kiotesthelper.h create mode 100644 autotests/klocalsocketservertest.cpp create mode 100644 autotests/klocalsocketservertest.h create mode 100644 autotests/klocalsockettest.cpp create mode 100644 autotests/klocalsockettest.h create mode 100644 autotests/kmountpointtest.cpp create mode 100644 autotests/kmountpointtest.h create mode 100644 autotests/knewfilemenutest.cpp create mode 100644 autotests/kprotocolinfotest.cpp create mode 100644 autotests/krununittest.cpp create mode 100644 autotests/krununittest.h create mode 100644 autotests/ktcpsockettest.cpp create mode 100644 autotests/ktcpsockettest.h create mode 100644 autotests/kurifiltersearchprovideractionstest.cpp create mode 100644 autotests/kurifiltersearchprovideractionstest.h create mode 100644 autotests/kurifiltertest.cpp create mode 100644 autotests/kurifiltertest.h create mode 100644 autotests/kurlcomboboxtest.cpp create mode 100644 autotests/kurlcomboboxtest.h create mode 100644 autotests/kurlcompletiontest.cpp create mode 100644 autotests/kurlnavigatortest.cpp create mode 100644 autotests/kurlnavigatortest.h create mode 100644 autotests/kurlrequestertest.cpp create mode 100644 autotests/listdirtest.cpp create mode 100644 autotests/listdirtest.h create mode 100644 autotests/mkpathjobtest.cpp create mode 100644 autotests/pastetest.cpp create mode 100644 autotests/pastetest.h create mode 100644 autotests/threadtest.cpp create mode 100644 autotests/udsentry_benchmark.cpp create mode 100644 autotests/udsentrytest.cpp create mode 100644 autotests/udsentrytest.h create mode 100644 autotests/upurltest.cpp create mode 100644 autotests/wronglocalsizes.zip create mode 100644 cmake/FindACL.cmake create mode 100644 cmake/FindGSSAPI.cmake create mode 100644 docs/CMakeLists.txt create mode 100644 docs/design.txt create mode 100644 docs/kcookiejar5/CMakeLists.txt create mode 100644 docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 docs/kioslave5/CMakeLists.txt create mode 100644 docs/kioslave5/data/CMakeLists.txt create mode 100644 docs/kioslave5/data/index.docbook create mode 100644 docs/kioslave5/file/CMakeLists.txt create mode 100644 docs/kioslave5/file/index.docbook create mode 100644 docs/kioslave5/ftp/CMakeLists.txt create mode 100644 docs/kioslave5/ftp/index.docbook create mode 100644 docs/kioslave5/help/CMakeLists.txt create mode 100644 docs/kioslave5/help/documentationnotfound/CMakeLists.txt create mode 100644 docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 docs/kioslave5/help/index.docbook create mode 100644 docs/kioslave5/http/CMakeLists.txt create mode 100644 docs/kioslave5/http/index.docbook create mode 100644 docs/kioslave5/mailto/CMakeLists.txt create mode 100644 docs/kioslave5/mailto/index.docbook create mode 100644 docs/kioslave5/telnet/CMakeLists.txt create mode 100644 docs/kioslave5/telnet/index.docbook create mode 100644 docs/kioslave5/webdav/CMakeLists.txt create mode 100644 docs/kioslave5/webdav/index.docbook create mode 100644 docs/krun-passing-slaves.txt create mode 100644 docs/metadata.txt create mode 100644 docs/pics/kpropertiesdialog.png create mode 100644 docs/pics/kurlrequester.png create mode 100644 metainfo.yaml create mode 100644 po/af/kio5.po create mode 100644 po/ar/kio5.po create mode 100644 po/ast/kio5.po create mode 100644 po/be/kio5.po create mode 100644 po/be@latin/kio5.po create mode 100644 po/bg/kio5.po create mode 100644 po/bn/kio5.po create mode 100644 po/br/kio5.po create mode 100644 po/bs/kio5.po create mode 100644 po/ca/docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 po/ca/docs/kioslave5/data/index.docbook create mode 100644 po/ca/docs/kioslave5/file/index.docbook create mode 100644 po/ca/docs/kioslave5/ftp/index.docbook create mode 100644 po/ca/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/ca/docs/kioslave5/help/index.docbook create mode 100644 po/ca/docs/kioslave5/http/index.docbook create mode 100644 po/ca/docs/kioslave5/mailto/index.docbook create mode 100644 po/ca/docs/kioslave5/telnet/index.docbook create mode 100644 po/ca/docs/kioslave5/webdav/index.docbook create mode 100644 po/ca/kio5.po create mode 100644 po/ca@valencia/kio5.po create mode 100644 po/cs/kio5.po create mode 100644 po/csb/kio5.po create mode 100644 po/cy/kio5.po create mode 100644 po/da/kio5.po create mode 100644 po/de/docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 po/de/docs/kioslave5/data/index.docbook create mode 100644 po/de/docs/kioslave5/file/index.docbook create mode 100644 po/de/docs/kioslave5/ftp/index.docbook create mode 100644 po/de/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/de/docs/kioslave5/help/index.docbook create mode 100644 po/de/docs/kioslave5/http/index.docbook create mode 100644 po/de/docs/kioslave5/mailto/index.docbook create mode 100644 po/de/docs/kioslave5/telnet/index.docbook create mode 100644 po/de/docs/kioslave5/webdav/index.docbook create mode 100644 po/de/kio5.po create mode 100644 po/el/kio5.po create mode 100644 po/en_GB/kio5.po create mode 100644 po/eo/kio5.po create mode 100644 po/es/docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 po/es/docs/kioslave5/data/index.docbook create mode 100644 po/es/docs/kioslave5/file/index.docbook create mode 100644 po/es/docs/kioslave5/ftp/index.docbook create mode 100644 po/es/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/es/docs/kioslave5/help/index.docbook create mode 100644 po/es/docs/kioslave5/http/index.docbook create mode 100644 po/es/docs/kioslave5/mailto/index.docbook create mode 100644 po/es/docs/kioslave5/telnet/index.docbook create mode 100644 po/es/docs/kioslave5/webdav/index.docbook create mode 100644 po/es/kio5.po create mode 100644 po/et/docs/kioslave5/data/index.docbook create mode 100644 po/et/docs/kioslave5/file/index.docbook create mode 100644 po/et/docs/kioslave5/ftp/index.docbook create mode 100644 po/et/docs/kioslave5/help/index.docbook create mode 100644 po/et/docs/kioslave5/http/index.docbook create mode 100644 po/et/docs/kioslave5/mailto/index.docbook create mode 100644 po/et/docs/kioslave5/telnet/index.docbook create mode 100644 po/et/docs/kioslave5/webdav/index.docbook create mode 100644 po/et/kio5.po create mode 100644 po/eu/kio5.po create mode 100644 po/fa/kio5.po create mode 100644 po/fi/kio5.po create mode 100644 po/fr/kio5.po create mode 100644 po/fy/kio5.po create mode 100644 po/ga/kio5.po create mode 100644 po/gl/kio5.po create mode 100644 po/gu/kio5.po create mode 100644 po/he/kio5.po create mode 100644 po/hi/kio5.po create mode 100644 po/hr/kio5.po create mode 100644 po/hsb/kio5.po create mode 100644 po/hu/kio5.po create mode 100644 po/ia/kio5.po create mode 100644 po/is/kio5.po create mode 100644 po/it/docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 po/it/docs/kioslave5/data/index.docbook create mode 100644 po/it/docs/kioslave5/file/index.docbook create mode 100644 po/it/docs/kioslave5/ftp/index.docbook create mode 100644 po/it/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/it/docs/kioslave5/help/index.docbook create mode 100644 po/it/docs/kioslave5/http/index.docbook create mode 100644 po/it/docs/kioslave5/mailto/index.docbook create mode 100644 po/it/docs/kioslave5/telnet/index.docbook create mode 100644 po/it/docs/kioslave5/webdav/index.docbook create mode 100644 po/it/kio5.po create mode 100644 po/ja/kio5.po create mode 100644 po/kk/kio5.po create mode 100644 po/km/kio5.po create mode 100644 po/ko/kio5.po create mode 100644 po/ku/kio5.po create mode 100644 po/lt/kio5.po create mode 100644 po/lv/kio5.po create mode 100644 po/mai/kio5.po create mode 100644 po/mk/kio5.po create mode 100644 po/ml/kio5.po create mode 100644 po/mr/kio5.po create mode 100644 po/ms/kio5.po create mode 100644 po/nb/kio5.po create mode 100644 po/nds/kio5.po create mode 100644 po/ne/kio5.po create mode 100644 po/nl/docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 po/nl/docs/kioslave5/data/index.docbook create mode 100644 po/nl/docs/kioslave5/file/index.docbook create mode 100644 po/nl/docs/kioslave5/ftp/index.docbook create mode 100644 po/nl/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/nl/docs/kioslave5/help/index.docbook create mode 100644 po/nl/docs/kioslave5/http/index.docbook create mode 100644 po/nl/docs/kioslave5/mailto/index.docbook create mode 100644 po/nl/docs/kioslave5/telnet/index.docbook create mode 100644 po/nl/docs/kioslave5/webdav/index.docbook create mode 100644 po/nl/kio5.po create mode 100644 po/nn/kio5.po create mode 100644 po/oc/kio5.po create mode 100644 po/pa/kio5.po create mode 100644 po/pl/kio5.po create mode 100644 po/pt/kio5.po create mode 100644 po/pt_BR/docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 po/pt_BR/docs/kioslave5/data/index.docbook create mode 100644 po/pt_BR/docs/kioslave5/file/index.docbook create mode 100644 po/pt_BR/docs/kioslave5/ftp/index.docbook create mode 100644 po/pt_BR/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/pt_BR/docs/kioslave5/help/index.docbook create mode 100644 po/pt_BR/docs/kioslave5/http/index.docbook create mode 100644 po/pt_BR/docs/kioslave5/mailto/index.docbook create mode 100644 po/pt_BR/docs/kioslave5/telnet/index.docbook create mode 100644 po/pt_BR/docs/kioslave5/webdav/index.docbook create mode 100644 po/pt_BR/kio5.po create mode 100644 po/ro/kio5.po create mode 100644 po/ru/docs/kioslave5/data/index.docbook create mode 100644 po/ru/docs/kioslave5/file/index.docbook create mode 100644 po/ru/docs/kioslave5/ftp/index.docbook create mode 100644 po/ru/docs/kioslave5/help/index.docbook create mode 100644 po/ru/docs/kioslave5/telnet/index.docbook create mode 100644 po/ru/docs/kioslave5/webdav/index.docbook create mode 100644 po/ru/kio5.po create mode 100644 po/se/kio5.po create mode 100644 po/sk/kio5.po create mode 100644 po/sl/kio5.po create mode 100644 po/sq/kio5.po create mode 100644 po/sr/docs/kioslave5/data/index.docbook create mode 100644 po/sr/docs/kioslave5/file/index.docbook create mode 100644 po/sr/docs/kioslave5/ftp/index.docbook create mode 100644 po/sr/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/sr/docs/kioslave5/help/index.docbook create mode 100644 po/sr/docs/kioslave5/http/index.docbook create mode 100644 po/sr/docs/kioslave5/mailto/index.docbook create mode 100644 po/sr/docs/kioslave5/telnet/index.docbook create mode 100644 po/sr/docs/kioslave5/webdav/index.docbook create mode 100644 po/sr/kio5.po create mode 100644 po/sr@ijekavian/kio5.po create mode 100644 po/sr@ijekavianlatin/kio5.po create mode 100644 po/sr@latin/docs/kioslave5/data/index.docbook create mode 100644 po/sr@latin/docs/kioslave5/file/index.docbook create mode 100644 po/sr@latin/docs/kioslave5/ftp/index.docbook create mode 100644 po/sr@latin/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/sr@latin/docs/kioslave5/help/index.docbook create mode 100644 po/sr@latin/docs/kioslave5/http/index.docbook create mode 100644 po/sr@latin/docs/kioslave5/mailto/index.docbook create mode 100644 po/sr@latin/docs/kioslave5/telnet/index.docbook create mode 100644 po/sr@latin/docs/kioslave5/webdav/index.docbook create mode 100644 po/sr@latin/kio5.po create mode 100644 po/sv/docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 po/sv/docs/kioslave5/data/index.docbook create mode 100644 po/sv/docs/kioslave5/file/index.docbook create mode 100644 po/sv/docs/kioslave5/ftp/index.docbook create mode 100644 po/sv/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/sv/docs/kioslave5/help/index.docbook create mode 100644 po/sv/docs/kioslave5/http/index.docbook create mode 100644 po/sv/docs/kioslave5/mailto/index.docbook create mode 100644 po/sv/docs/kioslave5/telnet/index.docbook create mode 100644 po/sv/docs/kioslave5/webdav/index.docbook create mode 100644 po/sv/kio5.po create mode 100644 po/ta/kio5.po create mode 100644 po/tg/kio5.po create mode 100644 po/th/kio5.po create mode 100644 po/tr/kio5.po create mode 100644 po/ug/kio5.po create mode 100644 po/uk/docs/kcookiejar5/man-kcookiejar5.8.docbook create mode 100644 po/uk/docs/kioslave5/data/index.docbook create mode 100644 po/uk/docs/kioslave5/file/index.docbook create mode 100644 po/uk/docs/kioslave5/ftp/index.docbook create mode 100644 po/uk/docs/kioslave5/help/documentationnotfound/index.docbook create mode 100644 po/uk/docs/kioslave5/help/index.docbook create mode 100644 po/uk/docs/kioslave5/http/index.docbook create mode 100644 po/uk/docs/kioslave5/mailto/index.docbook create mode 100644 po/uk/docs/kioslave5/telnet/index.docbook create mode 100644 po/uk/docs/kioslave5/webdav/index.docbook create mode 100644 po/uk/kio5.po create mode 100644 po/uz/kio5.po create mode 100644 po/uz@cyrillic/kio5.po create mode 100644 po/vi/kio5.po create mode 100644 po/wa/kio5.po create mode 100644 po/xh/kio5.po create mode 100644 po/zh_CN/kio5.po create mode 100644 po/zh_TW/kio5.po create mode 100644 src/CMakeLists.txt create mode 100644 src/Messages.sh create mode 100644 src/core/CMakeLists.txt create mode 100644 src/core/ConfigureChecks.cmake create mode 100644 src/core/accept-languages.codes create mode 100644 src/core/authinfo.cpp create mode 100644 src/core/authinfo.h create mode 100644 src/core/chmodjob.cpp create mode 100644 src/core/chmodjob.h create mode 100644 src/core/commands_p.h create mode 100644 src/core/config-kiocore.h.cmake create mode 100644 src/core/config-kmountpoint.h.cmake create mode 100644 src/core/connection.cpp create mode 100644 src/core/connection_p.h create mode 100644 src/core/connectionbackend.cpp create mode 100644 src/core/connectionbackend_p.h create mode 100644 src/core/connectionserver.cpp create mode 100644 src/core/connectionserver.h create mode 100644 src/core/copyjob.cpp create mode 100644 src/core/copyjob.h create mode 100644 src/core/dataprotocol.cpp create mode 100644 src/core/dataprotocol_p.h create mode 100644 src/core/dataslave.cpp create mode 100644 src/core/dataslave_p.h create mode 100644 src/core/davjob.cpp create mode 100644 src/core/davjob.h create mode 100644 src/core/deletejob.cpp create mode 100644 src/core/deletejob.h create mode 100644 src/core/desktopexecparser.cpp create mode 100644 src/core/desktopexecparser.h create mode 100644 src/core/directorysizejob.cpp create mode 100644 src/core/directorysizejob.h create mode 100644 src/core/emptytrashjob.cpp create mode 100644 src/core/emptytrashjob.h create mode 100644 src/core/faviconscache.cpp create mode 100644 src/core/faviconscache_p.h create mode 100644 src/core/filecopyjob.cpp create mode 100644 src/core/filecopyjob.h create mode 100644 src/core/filejob.cpp create mode 100644 src/core/filejob.h create mode 100644 src/core/filesystemfreespacejob.cpp create mode 100644 src/core/filesystemfreespacejob.h create mode 100644 src/core/forwardingslavebase.cpp create mode 100644 src/core/forwardingslavebase.h create mode 100644 src/core/global.cpp create mode 100644 src/core/global.h create mode 100644 src/core/hostinfo.cpp create mode 100644 src/core/hostinfo.h create mode 100644 src/core/http_slave_defaults.h create mode 100644 src/core/httpmethod_p.h create mode 100644 src/core/idleslave.cpp create mode 100644 src/core/idleslave.h create mode 100644 src/core/ioslave_defaults.h create mode 100644 src/core/job.cpp create mode 100644 src/core/job.h create mode 100644 src/core/job_base.h create mode 100644 src/core/job_error.cpp create mode 100644 src/core/job_p.h create mode 100644 src/core/jobclasses.h create mode 100644 src/core/jobtracker.cpp create mode 100644 src/core/jobtracker.h create mode 100644 src/core/jobuidelegateextension.cpp create mode 100644 src/core/jobuidelegateextension.h create mode 100644 src/core/jobuidelegatefactory.cpp create mode 100644 src/core/jobuidelegatefactory.h create mode 100644 src/core/kacl.cpp create mode 100644 src/core/kacl.h create mode 100644 src/core/kcoredirlister.cpp create mode 100644 src/core/kcoredirlister.h create mode 100644 src/core/kcoredirlister_p.h create mode 100644 src/core/kdirnotify.cpp create mode 100644 src/core/kdirnotify.h create mode 100644 src/core/kdiskfreespaceinfo.cpp create mode 100644 src/core/kdiskfreespaceinfo.h create mode 100644 src/core/kfileitem.cpp create mode 100644 src/core/kfileitem.h create mode 100644 src/core/kfileitemlistproperties.cpp create mode 100644 src/core/kfileitemlistproperties.h create mode 100644 src/core/kiocoredebug.cpp create mode 100644 src/core/kiocoredebug.h create mode 100644 src/core/kioglobal_p.h create mode 100644 src/core/kioglobal_p_unix.cpp create mode 100644 src/core/kioglobal_p_win.cpp create mode 100644 src/core/klocalsocket.cpp create mode 100644 src/core/klocalsocket.h create mode 100644 src/core/klocalsocket_p.h create mode 100644 src/core/klocalsocket_unix.cpp create mode 100644 src/core/klocalsocket_win.cpp create mode 100644 src/core/kmountpoint.cpp create mode 100644 src/core/kmountpoint.h create mode 100644 src/core/knfsshare.cpp create mode 100644 src/core/knfsshare.h create mode 100644 src/core/kpasswdserverclient.cpp create mode 100644 src/core/kpasswdserverclient_p.h create mode 100644 src/core/kpasswdserverloop.cpp create mode 100644 src/core/kpasswdserverloop_p.h create mode 100644 src/core/kprotocolinfo.cpp create mode 100644 src/core/kprotocolinfo.h create mode 100644 src/core/kprotocolinfo_p.h create mode 100644 src/core/kprotocolinfofactory.cpp create mode 100644 src/core/kprotocolinfofactory_p.h create mode 100644 src/core/kprotocolmanager.cpp create mode 100644 src/core/kprotocolmanager.h create mode 100644 src/core/krecentdocument.cpp create mode 100644 src/core/krecentdocument.h create mode 100644 src/core/kremoteencoding.cpp create mode 100644 src/core/kremoteencoding.h create mode 100644 src/core/ksambashare.cpp create mode 100644 src/core/ksambashare.h create mode 100644 src/core/ksambashare_p.h create mode 100644 src/core/ksambasharedata.cpp create mode 100644 src/core/ksambasharedata.h create mode 100644 src/core/ksambasharedata_p.h create mode 100644 src/core/kssl/ksslsettings.cpp create mode 100644 src/core/kssl/ksslsettings.h create mode 100644 src/core/ksslcertificatemanager.cpp create mode 100644 src/core/ksslcertificatemanager.h create mode 100644 src/core/ksslcertificatemanager_p.h create mode 100644 src/core/kssld_dbusmetatypes.h create mode 100644 src/core/kssld_interface.h create mode 100644 src/core/ktcpsocket.cpp create mode 100644 src/core/ktcpsocket.h create mode 100644 src/core/ktcpsocket_p.h create mode 100644 src/core/kurlauthorized.cpp create mode 100644 src/core/kurlauthorized.h create mode 100644 src/core/listjob.cpp create mode 100644 src/core/listjob.h create mode 100644 src/core/metadata.cpp create mode 100644 src/core/metadata.h create mode 100644 src/core/mimetypejob.cpp create mode 100644 src/core/mimetypejob.h create mode 100644 src/core/mkdirjob.cpp create mode 100644 src/core/mkdirjob.h create mode 100644 src/core/mkpathjob.cpp create mode 100644 src/core/mkpathjob.h create mode 100644 src/core/multigetjob.cpp create mode 100644 src/core/multigetjob.h create mode 100644 src/core/org.kde.KDirNotify.xml create mode 100644 src/core/org.kde.KPasswdServer.xml create mode 100644 src/core/org.kde.KSlaveLauncher.xml create mode 100644 src/core/restorejob.cpp create mode 100644 src/core/restorejob.h create mode 100644 src/core/scheduler.cpp create mode 100644 src/core/scheduler.h create mode 100644 src/core/scheduler_p.h create mode 100644 src/core/sessiondata.cpp create mode 100644 src/core/sessiondata_p.h create mode 100644 src/core/simplejob.cpp create mode 100644 src/core/simplejob.h create mode 100644 src/core/slave.cpp create mode 100644 src/core/slave.h create mode 100644 src/core/slavebase.cpp create mode 100644 src/core/slavebase.h create mode 100644 src/core/slaveconfig.cpp create mode 100644 src/core/slaveconfig.h create mode 100644 src/core/slaveinterface.cpp create mode 100644 src/core/slaveinterface.h create mode 100644 src/core/slaveinterface_p.h create mode 100644 src/core/specialjob.cpp create mode 100644 src/core/specialjob.h create mode 100644 src/core/statjob.cpp create mode 100644 src/core/statjob.h create mode 100644 src/core/storedtransferjob.cpp create mode 100644 src/core/storedtransferjob.h create mode 100644 src/core/tcpslavebase.cpp create mode 100644 src/core/tcpslavebase.h create mode 100644 src/core/transferjob.cpp create mode 100644 src/core/transferjob.h create mode 100644 src/core/udsentry.cpp create mode 100644 src/core/udsentry.h create mode 100644 src/core/usernotificationhandler.cpp create mode 100644 src/core/usernotificationhandler_p.h create mode 100644 src/filewidgets/CMakeLists.txt create mode 100644 src/filewidgets/Mainpage.dox create mode 100644 src/filewidgets/config-kiofilewidgets.h.cmake create mode 100644 src/filewidgets/defaults-kfile.h create mode 100644 src/filewidgets/defaultviewadapter.cpp create mode 100644 src/filewidgets/defaultviewadapter_p.h create mode 100644 src/filewidgets/kabstractviewadapter.h create mode 100644 src/filewidgets/kdiroperator.cpp create mode 100644 src/filewidgets/kdiroperator.h create mode 100644 src/filewidgets/kdiroperatordetailview.cpp create mode 100644 src/filewidgets/kdiroperatordetailview_p.h create mode 100644 src/filewidgets/kdirsortfilterproxymodel.cpp create mode 100644 src/filewidgets/kdirsortfilterproxymodel.h create mode 100644 src/filewidgets/kencodingfiledialog.cpp create mode 100644 src/filewidgets/kencodingfiledialog.h create mode 100644 src/filewidgets/kfilebookmarkhandler.cpp create mode 100644 src/filewidgets/kfilebookmarkhandler_p.h create mode 100644 src/filewidgets/kfilecopytomenu.cpp create mode 100644 src/filewidgets/kfilecopytomenu.h create mode 100644 src/filewidgets/kfilecopytomenu_p.h create mode 100644 src/filewidgets/kfilefiltercombo.cpp create mode 100644 src/filewidgets/kfilefiltercombo.h create mode 100644 src/filewidgets/kfilemetapreview.cpp create mode 100644 src/filewidgets/kfilemetapreview_p.h create mode 100644 src/filewidgets/kfileplaceeditdialog.cpp create mode 100644 src/filewidgets/kfileplaceeditdialog.h create mode 100644 src/filewidgets/kfileplacesitem.cpp create mode 100644 src/filewidgets/kfileplacesitem_p.h create mode 100644 src/filewidgets/kfileplacesmodel.cpp create mode 100644 src/filewidgets/kfileplacesmodel.h create mode 100644 src/filewidgets/kfileplacesview.cpp create mode 100644 src/filewidgets/kfileplacesview.h create mode 100644 src/filewidgets/kfileplacesview_p.h create mode 100644 src/filewidgets/kfilepreviewgenerator.cpp create mode 100644 src/filewidgets/kfilepreviewgenerator.h create mode 100644 src/filewidgets/kfilewidget.cpp create mode 100644 src/filewidgets/kfilewidget.h create mode 100644 src/filewidgets/kimagefilepreview.cpp create mode 100644 src/filewidgets/kimagefilepreview.h create mode 100644 src/filewidgets/knameandurlinputdialog.cpp create mode 100644 src/filewidgets/knameandurlinputdialog.h create mode 100644 src/filewidgets/knewfilemenu.cpp create mode 100644 src/filewidgets/knewfilemenu.h create mode 100644 src/filewidgets/kpreviewwidgetbase.cpp create mode 100644 src/filewidgets/kpreviewwidgetbase.h create mode 100644 src/filewidgets/krecentdirs.cpp create mode 100644 src/filewidgets/krecentdirs.h create mode 100644 src/filewidgets/kstatusbarofflineindicator.cpp create mode 100644 src/filewidgets/kstatusbarofflineindicator.h create mode 100644 src/filewidgets/kurlnavigator.cpp create mode 100644 src/filewidgets/kurlnavigator.h create mode 100644 src/filewidgets/kurlnavigatorbutton.cpp create mode 100644 src/filewidgets/kurlnavigatorbutton_p.h create mode 100644 src/filewidgets/kurlnavigatorbuttonbase.cpp create mode 100644 src/filewidgets/kurlnavigatorbuttonbase_p.h create mode 100644 src/filewidgets/kurlnavigatordropdownbutton.cpp create mode 100644 src/filewidgets/kurlnavigatordropdownbutton_p.h create mode 100644 src/filewidgets/kurlnavigatormenu.cpp create mode 100644 src/filewidgets/kurlnavigatormenu_p.h create mode 100644 src/filewidgets/kurlnavigatorplacesselector.cpp create mode 100644 src/filewidgets/kurlnavigatorplacesselector_p.h create mode 100644 src/filewidgets/kurlnavigatorprotocolcombo.cpp create mode 100644 src/filewidgets/kurlnavigatorprotocolcombo_p.h create mode 100644 src/filewidgets/kurlnavigatortogglebutton.cpp create mode 100644 src/filewidgets/kurlnavigatortogglebutton_p.h create mode 100644 src/gui/CMakeLists.txt create mode 100644 src/gui/faviconrequestjob.cpp create mode 100644 src/gui/faviconrequestjob.h create mode 100644 src/ioslaves/CMakeLists.txt create mode 100644 src/ioslaves/DEBUG.howto create mode 100644 src/ioslaves/Mainpage.dox create mode 100644 src/ioslaves/file/CMakeLists.txt create mode 100644 src/ioslaves/file/ConfigureChecks.cmake create mode 100644 src/ioslaves/file/config-kioslave-file.h.cmake create mode 100644 src/ioslaves/file/file.cpp create mode 100644 src/ioslaves/file/file.h create mode 100644 src/ioslaves/file/file.json create mode 100644 src/ioslaves/file/file_unix.cpp create mode 100644 src/ioslaves/file/file_win.cpp create mode 100644 src/ioslaves/ftp/CMakeLists.txt create mode 100644 src/ioslaves/ftp/ConfigureChecks.cmake create mode 100644 src/ioslaves/ftp/config-kioslave-ftp.h.cmake create mode 100644 src/ioslaves/ftp/ftp.cpp create mode 100644 src/ioslaves/ftp/ftp.h create mode 100644 src/ioslaves/ftp/ftp.json create mode 100644 src/ioslaves/help/CMakeLists.txt create mode 100644 src/ioslaves/help/ConfigureChecks.cmake create mode 100644 src/ioslaves/help/config-help.h.cmake create mode 100644 src/ioslaves/help/ghelp.json create mode 100644 src/ioslaves/help/help.json create mode 100644 src/ioslaves/help/kio_help.cpp create mode 100644 src/ioslaves/help/kio_help.h create mode 100644 src/ioslaves/help/main.cpp create mode 100644 src/ioslaves/help/main_ghelp.cpp create mode 100644 src/ioslaves/help/xslt_help.cpp create mode 100644 src/ioslaves/help/xslt_help.h create mode 100644 src/ioslaves/http/CMakeLists.txt create mode 100644 src/ioslaves/http/ConfigureChecks.cmake create mode 100644 src/ioslaves/http/README.http_cache_cleaner create mode 100644 src/ioslaves/http/README.webdav create mode 100644 src/ioslaves/http/RFCs create mode 100644 src/ioslaves/http/THOUGHTS create mode 100644 src/ioslaves/http/TODO create mode 100644 src/ioslaves/http/config-gssapi.h.cmake create mode 100644 src/ioslaves/http/config-kioslave-http.h.cmake create mode 100644 src/ioslaves/http/http.cpp create mode 100644 src/ioslaves/http/http.h create mode 100644 src/ioslaves/http/http.json create mode 100644 src/ioslaves/http/http_cache_cleaner.cpp create mode 100644 src/ioslaves/http/http_cache_cleaner.desktop create mode 100644 src/ioslaves/http/httpauthentication.cpp create mode 100644 src/ioslaves/http/httpauthentication.h create mode 100644 src/ioslaves/http/httpfilter.cpp create mode 100644 src/ioslaves/http/httpfilter.h create mode 100644 src/ioslaves/http/kcookiejar/CMakeLists.txt create mode 100644 src/ioslaves/http/kcookiejar/domain_info create mode 100644 src/ioslaves/http/kcookiejar/kcookiejar.cpp create mode 100644 src/ioslaves/http/kcookiejar/kcookiejar.h create mode 100644 src/ioslaves/http/kcookiejar/kcookiejar.json create mode 100644 src/ioslaves/http/kcookiejar/kcookiejar_include.h create mode 100644 src/ioslaves/http/kcookiejar/kcookieserver.cpp create mode 100644 src/ioslaves/http/kcookiejar/kcookieserver.h create mode 100644 src/ioslaves/http/kcookiejar/kcookiewin.cpp create mode 100644 src/ioslaves/http/kcookiejar/kcookiewin.h create mode 100644 src/ioslaves/http/kcookiejar/main.cpp create mode 100644 src/ioslaves/http/kcookiejar/netscape_cookie_spec.html create mode 100644 src/ioslaves/http/kcookiejar/org.kde.kcookiejar5.service.in create mode 100644 src/ioslaves/http/kcookiejar/specifications create mode 100644 src/ioslaves/http/parsinghelpers.cpp create mode 100644 src/ioslaves/http/parsinghelpers.h create mode 100644 src/ioslaves/http/shoutcast-icecast.txt create mode 100644 src/ioslaves/protocols/CMakeLists.txt create mode 100644 src/ioslaves/protocols/data.protocol create mode 100644 src/ioslaves/protocols/mms.protocol create mode 100644 src/ioslaves/protocols/mmst.protocol create mode 100644 src/ioslaves/protocols/mmsu.protocol create mode 100644 src/ioslaves/protocols/pnm.protocol create mode 100644 src/ioslaves/protocols/rtsp.protocol create mode 100644 src/ioslaves/protocols/rtspt.protocol create mode 100644 src/ioslaves/protocols/rtspu.protocol create mode 100644 src/ioslaves/telnet/CMakeLists.txt create mode 100644 src/ioslaves/telnet/ktelnetservice.cpp create mode 100755 src/ioslaves/telnet/ktelnetservice5.desktop create mode 100644 src/ioslaves/trash/CMakeLists.txt create mode 100644 src/ioslaves/trash/DESIGN create mode 100644 src/ioslaves/trash/discspaceutil.cpp create mode 100644 src/ioslaves/trash/discspaceutil.h create mode 100644 src/ioslaves/trash/kcmtrash.cpp create mode 100644 src/ioslaves/trash/kcmtrash.desktop create mode 100644 src/ioslaves/trash/kcmtrash.h create mode 100644 src/ioslaves/trash/kinterprocesslock.cpp create mode 100644 src/ioslaves/trash/kinterprocesslock.h create mode 100644 src/ioslaves/trash/kio_trash.cpp create mode 100644 src/ioslaves/trash/kio_trash.h create mode 100644 src/ioslaves/trash/kio_trash_win.cpp create mode 100644 src/ioslaves/trash/kio_trash_win.h create mode 100644 src/ioslaves/trash/ktrash.cpp create mode 100644 src/ioslaves/trash/tests/CMakeLists.txt create mode 100644 src/ioslaves/trash/tests/lockingtest.cpp create mode 100644 src/ioslaves/trash/tests/testtrash.cpp create mode 100644 src/ioslaves/trash/tests/testtrash.h create mode 100644 src/ioslaves/trash/trash.json create mode 100644 src/ioslaves/trash/trashimpl.cpp create mode 100644 src/ioslaves/trash/trashimpl.h create mode 100644 src/ioslaves/trash/trashsizecache.cpp create mode 100644 src/ioslaves/trash/trashsizecache.h create mode 100644 src/kcms/CMakeLists.txt create mode 100644 src/kcms/kio/CMakeLists.txt create mode 100644 src/kcms/kio/UA-DESKTOP-FILE-HOWTO create mode 100644 src/kcms/kio/cache.cpp create mode 100644 src/kcms/kio/cache.desktop create mode 100644 src/kcms/kio/cache.h create mode 100644 src/kcms/kio/cache.ui create mode 100644 src/kcms/kio/cookies.desktop create mode 100644 src/kcms/kio/kcookiesmain.cpp create mode 100644 src/kcms/kio/kcookiesmain.h create mode 100644 src/kcms/kio/kcookiesmanagement.cpp create mode 100644 src/kcms/kio/kcookiesmanagement.h create mode 100644 src/kcms/kio/kcookiesmanagement.ui create mode 100644 src/kcms/kio/kcookiespolicies.cpp create mode 100644 src/kcms/kio/kcookiespolicies.h create mode 100644 src/kcms/kio/kcookiespolicies.ui create mode 100644 src/kcms/kio/kcookiespolicyselectiondlg.cpp create mode 100644 src/kcms/kio/kcookiespolicyselectiondlg.h create mode 100644 src/kcms/kio/kcookiespolicyselectiondlg.ui create mode 100644 src/kcms/kio/kio_ftprc.kcfg create mode 100644 src/kcms/kio/kio_ftprc.kcfgc create mode 100644 src/kcms/kio/kioslave.kcfg create mode 100644 src/kcms/kio/kioslave.kcfgc create mode 100644 src/kcms/kio/kproxydlg.cpp create mode 100644 src/kcms/kio/kproxydlg.h create mode 100644 src/kcms/kio/kproxydlg.ui create mode 100644 src/kcms/kio/ksaveioconfig.cpp create mode 100644 src/kcms/kio/ksaveioconfig.h create mode 100644 src/kcms/kio/main.cpp create mode 100644 src/kcms/kio/netpref.cpp create mode 100644 src/kcms/kio/netpref.desktop create mode 100644 src/kcms/kio/netpref.h create mode 100644 src/kcms/kio/proxy.desktop create mode 100644 src/kcms/kio/smb.desktop create mode 100644 src/kcms/kio/smbrodlg.cpp create mode 100644 src/kcms/kio/smbrodlg.h create mode 100644 src/kcms/kio/uasprovider.desktop create mode 100644 src/kcms/kio/uasproviders/CMakeLists.txt create mode 100644 src/kcms/kio/uasproviders/android10.desktop create mode 100644 src/kcms/kio/uasproviders/chrome10onwinnt51.desktop create mode 100644 src/kcms/kio/uasproviders/chrome22oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/chrome23oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/chrome24oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/chrome50oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/firefox15oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/firefox16oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/firefox20oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/firefox30oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/firefox36oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/googlebot.desktop create mode 100644 src/kcms/kio/uasproviders/ie401onwinnt4.desktop create mode 100644 src/kcms/kio/uasproviders/ie50onppc.desktop create mode 100644 src/kcms/kio/uasproviders/ie55onwinnt5.desktop create mode 100644 src/kcms/kio/uasproviders/ie60oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/ie60onwinnt51.desktop create mode 100644 src/kcms/kio/uasproviders/ie70onwinnt51.desktop create mode 100644 src/kcms/kio/uasproviders/ie80onwinnt60.desktop create mode 100644 src/kcms/kio/uasproviders/ie90onwinnt71.desktop create mode 100644 src/kcms/kio/uasproviders/lynxoncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/nn301oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/nn475oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/nn475onwin95.desktop create mode 100644 src/kcms/kio/uasproviders/ns71oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/ns71onwinnt51.desktop create mode 100644 src/kcms/kio/uasproviders/op1162oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/op1202oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/op403onwinnt4.desktop create mode 100644 src/kcms/kio/uasproviders/op85oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/op90oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/op962oncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/safari20.desktop create mode 100644 src/kcms/kio/uasproviders/safari30oniphone.desktop create mode 100644 src/kcms/kio/uasproviders/safari32.desktop create mode 100644 src/kcms/kio/uasproviders/safari40.desktop create mode 100644 src/kcms/kio/uasproviders/safari517.desktop create mode 100644 src/kcms/kio/uasproviders/safari60.desktop create mode 100644 src/kcms/kio/uasproviders/w3moncurrent.desktop create mode 100644 src/kcms/kio/uasproviders/wgetoncurrent.desktop create mode 100644 src/kcms/kio/useragent.desktop create mode 100644 src/kcms/kio/useragentdlg.cpp create mode 100644 src/kcms/kio/useragentdlg.h create mode 100644 src/kcms/kio/useragentdlg.ui create mode 100644 src/kcms/kio/useragentinfo.cpp create mode 100644 src/kcms/kio/useragentinfo.h create mode 100644 src/kcms/kio/useragentselectordlg.cpp create mode 100644 src/kcms/kio/useragentselectordlg.h create mode 100644 src/kcms/kio/useragentselectordlg.ui create mode 100644 src/kcms/webshortcuts/CMakeLists.txt create mode 100644 src/kcms/webshortcuts/main.cpp create mode 100644 src/kcms/webshortcuts/main.h create mode 100644 src/kcms/webshortcuts/webshortcuts.desktop create mode 100644 src/kiod/CMakeLists.txt create mode 100644 src/kiod/kiod_main.cpp create mode 100644 src/kiod/org.kde.kiod5.service.in create mode 100644 src/kioexec/CMakeLists.txt create mode 100644 src/kioexec/README create mode 100644 src/kioexec/config-kioexec.h.cmake create mode 100644 src/kioexec/main.cpp create mode 100644 src/kioexec/main.h create mode 100644 src/kioslave/CMakeLists.txt create mode 100644 src/kioslave/kioslave.cpp create mode 100644 src/kntlm/CMakeLists.txt create mode 100644 src/kntlm/des.cpp create mode 100644 src/kntlm/des.h create mode 100644 src/kntlm/kntlm.cpp create mode 100644 src/kntlm/kntlm.h create mode 100644 src/kpac/CMakeLists.txt create mode 100644 src/kpac/ConfigureChecks.cmake create mode 100644 src/kpac/README create mode 100644 src/kpac/README.wpad create mode 100644 src/kpac/TODO create mode 100644 src/kpac/config-kpac.h.cmake create mode 100644 src/kpac/dhcp.h create mode 100644 src/kpac/discovery.cpp create mode 100644 src/kpac/discovery.h create mode 100644 src/kpac/downloader.cpp create mode 100644 src/kpac/downloader.h create mode 100644 src/kpac/kpac_dhcp_helper.c create mode 100644 src/kpac/kpactest.pac create mode 100644 src/kpac/kpactest2.pac create mode 100644 src/kpac/proxyscout.cpp create mode 100644 src/kpac/proxyscout.h create mode 100644 src/kpac/proxyscout.json create mode 100644 src/kpac/proxyscout.notifyrc create mode 100644 src/kpac/script.cpp create mode 100644 src/kpac/script.h create mode 100644 src/kpasswdserver/CMakeLists.txt create mode 100644 src/kpasswdserver/DESIGN create mode 100644 src/kpasswdserver/autotests/CMakeLists.txt create mode 100644 src/kpasswdserver/autotests/kpasswdservertest.cpp create mode 100644 src/kpasswdserver/kiod_kpasswdserver.cpp create mode 100644 src/kpasswdserver/kpasswdserver.cpp create mode 100644 src/kpasswdserver/kpasswdserver.h create mode 100644 src/kpasswdserver/kpasswdserver.json create mode 100644 src/kpasswdserver/org.kde.kpasswdserver.service.in create mode 100644 src/kssld/CMakeLists.txt create mode 100644 src/kssld/kssld.cpp create mode 100644 src/kssld/kssld.h create mode 100644 src/kssld/kssld.json create mode 100644 src/kssld/kssld_adaptor.h create mode 100644 src/kssld/org.kde.kssld5.service.in create mode 100644 src/new_file_templates/Directory.desktop create mode 100644 src/new_file_templates/HTMLFile.desktop create mode 100644 src/new_file_templates/HTMLFile.html create mode 100644 src/new_file_templates/Program.desktop create mode 100644 src/new_file_templates/TextFile.desktop create mode 100644 src/new_file_templates/TextFile.txt create mode 100644 src/new_file_templates/URL.desktop create mode 100644 src/new_file_templates/linkPath.desktop create mode 100644 src/new_file_templates/linkProgram.desktop create mode 100644 src/new_file_templates/linkURL.desktop create mode 100644 src/new_file_templates/templates.qrc create mode 100644 src/protocoltojson/CMakeLists.txt create mode 100644 src/protocoltojson/main.cpp create mode 100644 src/urifilters/CMakeLists.txt create mode 100644 src/urifilters/fixhost/CMakeLists.txt create mode 100644 src/urifilters/fixhost/fixhosturifilter.cpp create mode 100644 src/urifilters/fixhost/fixhosturifilter.desktop create mode 100644 src/urifilters/fixhost/fixhosturifilter.h create mode 100644 src/urifilters/ikws/CMakeLists.txt create mode 100644 src/urifilters/ikws/ikwsopts.cpp create mode 100644 src/urifilters/ikws/ikwsopts.h create mode 100644 src/urifilters/ikws/ikwsopts_p.h create mode 100644 src/urifilters/ikws/ikwsopts_ui.ui create mode 100644 src/urifilters/ikws/kuriikwsfilter.cpp create mode 100644 src/urifilters/ikws/kuriikwsfilter.desktop create mode 100644 src/urifilters/ikws/kuriikwsfilter.h create mode 100644 src/urifilters/ikws/kuriikwsfiltereng.cpp create mode 100644 src/urifilters/ikws/kuriikwsfiltereng.h create mode 100644 src/urifilters/ikws/kurisearchfilter.cpp create mode 100644 src/urifilters/ikws/kurisearchfilter.desktop create mode 100644 src/urifilters/ikws/kurisearchfilter.h create mode 100644 src/urifilters/ikws/searchprovider.cpp create mode 100644 src/urifilters/ikws/searchprovider.desktop create mode 100644 src/urifilters/ikws/searchprovider.h create mode 100644 src/urifilters/ikws/searchproviderdlg.cpp create mode 100644 src/urifilters/ikws/searchproviderdlg.h create mode 100644 src/urifilters/ikws/searchproviderdlg_ui.ui create mode 100644 src/urifilters/ikws/searchproviders/7digital.desktop create mode 100644 src/urifilters/ikws/searchproviders/CMakeLists.txt create mode 100644 src/urifilters/ikws/searchproviders/acronym.desktop create mode 100644 src/urifilters/ikws/searchproviders/amazon.desktop create mode 100644 src/urifilters/ikws/searchproviders/amazon_mp3.desktop create mode 100644 src/urifilters/ikws/searchproviders/amg.desktop create mode 100644 src/urifilters/ikws/searchproviders/archpkg.desktop create mode 100644 src/urifilters/ikws/searchproviders/backports.desktop create mode 100644 src/urifilters/ikws/searchproviders/baidu.desktop create mode 100644 src/urifilters/ikws/searchproviders/beolingus.desktop create mode 100644 src/urifilters/ikws/searchproviders/bing.desktop create mode 100644 src/urifilters/ikws/searchproviders/blip.desktop create mode 100644 src/urifilters/ikws/searchproviders/bugft.desktop create mode 100644 src/urifilters/ikws/searchproviders/bugno.desktop create mode 100644 src/urifilters/ikws/searchproviders/call.desktop create mode 100644 src/urifilters/ikws/searchproviders/cia.desktop create mode 100644 src/urifilters/ikws/searchproviders/citeseer.desktop create mode 100644 src/urifilters/ikws/searchproviders/cpan.desktop create mode 100644 src/urifilters/ikws/searchproviders/ctan.desktop create mode 100644 src/urifilters/ikws/searchproviders/ctan_cat.desktop create mode 100644 src/urifilters/ikws/searchproviders/dbug.desktop create mode 100644 src/urifilters/ikws/searchproviders/de2en.desktop create mode 100644 src/urifilters/ikws/searchproviders/de2fr.desktop create mode 100644 src/urifilters/ikws/searchproviders/deb.desktop create mode 100644 src/urifilters/ikws/searchproviders/dictfr.desktop create mode 100644 src/urifilters/ikws/searchproviders/dmoz.desktop create mode 100644 src/urifilters/ikws/searchproviders/docbook.desktop create mode 100644 src/urifilters/ikws/searchproviders/doi.desktop create mode 100644 src/urifilters/ikws/searchproviders/duckduckgo.desktop create mode 100644 src/urifilters/ikws/searchproviders/duckduckgo_info.desktop create mode 100644 src/urifilters/ikws/searchproviders/duckduckgo_shopping.desktop create mode 100644 src/urifilters/ikws/searchproviders/ecosia.desktop create mode 100644 src/urifilters/ikws/searchproviders/en2de.desktop create mode 100644 src/urifilters/ikws/searchproviders/en2es.desktop create mode 100644 src/urifilters/ikws/searchproviders/en2fr.desktop create mode 100644 src/urifilters/ikws/searchproviders/en2it.desktop create mode 100644 src/urifilters/ikws/searchproviders/es2en.desktop create mode 100644 src/urifilters/ikws/searchproviders/ethicle.desktop create mode 100644 src/urifilters/ikws/searchproviders/facebook.desktop create mode 100644 src/urifilters/ikws/searchproviders/feedster.desktop create mode 100644 src/urifilters/ikws/searchproviders/flickr.desktop create mode 100644 src/urifilters/ikws/searchproviders/flickrcc.desktop create mode 100644 src/urifilters/ikws/searchproviders/foldoc.desktop create mode 100644 src/urifilters/ikws/searchproviders/fr2de.desktop create mode 100644 src/urifilters/ikws/searchproviders/fr2en.desktop create mode 100644 src/urifilters/ikws/searchproviders/freecode.desktop create mode 100644 src/urifilters/ikws/searchproviders/freedb.desktop create mode 100644 src/urifilters/ikws/searchproviders/fsd.desktop create mode 100644 src/urifilters/ikws/searchproviders/github.desktop create mode 100644 src/urifilters/ikws/searchproviders/gitorious.desktop create mode 100644 src/urifilters/ikws/searchproviders/google.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_advanced.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_code.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_groups.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_images.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_lucky.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_maps.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_movie.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_news.desktop create mode 100644 src/urifilters/ikws/searchproviders/google_shopping.desktop create mode 100644 src/urifilters/ikws/searchproviders/grec.desktop create mode 100644 src/urifilters/ikws/searchproviders/hyperdictionary.desktop create mode 100644 src/urifilters/ikws/searchproviders/hyperdictionary_thesaurus.desktop create mode 100644 src/urifilters/ikws/searchproviders/ibl.desktop create mode 100644 src/urifilters/ikws/searchproviders/identica_groups.desktop create mode 100644 src/urifilters/ikws/searchproviders/identica_notices.desktop create mode 100644 src/urifilters/ikws/searchproviders/identica_people.desktop create mode 100644 src/urifilters/ikws/searchproviders/imdb.desktop create mode 100644 src/urifilters/ikws/searchproviders/it2en.desktop create mode 100644 src/urifilters/ikws/searchproviders/jamendo.desktop create mode 100644 src/urifilters/ikws/searchproviders/jeeves.desktop create mode 100644 src/urifilters/ikws/searchproviders/kde.desktop create mode 100644 src/urifilters/ikws/searchproviders/kde_apps.desktop create mode 100644 src/urifilters/ikws/searchproviders/kde_forums.desktop create mode 100644 src/urifilters/ikws/searchproviders/kde_look.desktop create mode 100644 src/urifilters/ikws/searchproviders/kde_projects.desktop create mode 100644 src/urifilters/ikws/searchproviders/kde_techbase.desktop create mode 100644 src/urifilters/ikws/searchproviders/kde_userbase.desktop create mode 100644 src/urifilters/ikws/searchproviders/leo.desktop create mode 100644 src/urifilters/ikws/searchproviders/magnatune.desktop create mode 100644 src/urifilters/ikws/searchproviders/metacrawler.desktop create mode 100644 src/urifilters/ikws/searchproviders/msdn.desktop create mode 100644 src/urifilters/ikws/searchproviders/multitran-deru.desktop create mode 100644 src/urifilters/ikws/searchproviders/multitran-enru.desktop create mode 100644 src/urifilters/ikws/searchproviders/multitran-esru.desktop create mode 100644 src/urifilters/ikws/searchproviders/multitran-frru.desktop create mode 100644 src/urifilters/ikws/searchproviders/multitran-itru.desktop create mode 100644 src/urifilters/ikws/searchproviders/multitran-nlru.desktop create mode 100644 src/urifilters/ikws/searchproviders/netcraft.desktop create mode 100644 src/urifilters/ikws/searchproviders/nl-telephone.desktop create mode 100644 src/urifilters/ikws/searchproviders/nl-teletekst.desktop create mode 100644 src/urifilters/ikws/searchproviders/opendesktop.desktop create mode 100644 src/urifilters/ikws/searchproviders/pgpkeys.desktop create mode 100644 src/urifilters/ikws/searchproviders/php.desktop create mode 100644 src/urifilters/ikws/searchproviders/python.desktop create mode 100644 src/urifilters/ikws/searchproviders/qt.desktop create mode 100644 src/urifilters/ikws/searchproviders/qt4.desktop create mode 100644 src/urifilters/ikws/searchproviders/qwant.desktop create mode 100644 src/urifilters/ikws/searchproviders/qwant_images.desktop create mode 100644 src/urifilters/ikws/searchproviders/qwant_news.desktop create mode 100644 src/urifilters/ikws/searchproviders/qwant_shopping.desktop create mode 100644 src/urifilters/ikws/searchproviders/qwant_social.desktop create mode 100644 src/urifilters/ikws/searchproviders/qwant_videos.desktop create mode 100644 src/urifilters/ikws/searchproviders/rae.desktop create mode 100644 src/urifilters/ikws/searchproviders/rag.desktop create mode 100644 src/urifilters/ikws/searchproviders/rfc.desktop create mode 100644 src/urifilters/ikws/searchproviders/rpmfind.desktop create mode 100644 src/urifilters/ikws/searchproviders/ruby_application_archive.desktop create mode 100644 src/urifilters/ikws/searchproviders/soundcloud.desktop create mode 100644 src/urifilters/ikws/searchproviders/sourceforge.desktop create mode 100644 src/urifilters/ikws/searchproviders/technorati.desktop create mode 100644 src/urifilters/ikws/searchproviders/technoratitags.desktop create mode 100644 src/urifilters/ikws/searchproviders/thesaurus.desktop create mode 100644 src/urifilters/ikws/searchproviders/tvtome.desktop create mode 100644 src/urifilters/ikws/searchproviders/urbandictionary.desktop create mode 100644 src/urifilters/ikws/searchproviders/uspto.desktop create mode 100644 src/urifilters/ikws/searchproviders/vimeo.desktop create mode 100644 src/urifilters/ikws/searchproviders/voila.desktop create mode 100644 src/urifilters/ikws/searchproviders/webster.desktop create mode 100644 src/urifilters/ikws/searchproviders/wikia.desktop create mode 100644 src/urifilters/ikws/searchproviders/wikipedia.desktop create mode 100644 src/urifilters/ikws/searchproviders/wiktionary.desktop create mode 100644 src/urifilters/ikws/searchproviders/wolfram_alpha.desktop create mode 100644 src/urifilters/ikws/searchproviders/wordref.desktop create mode 100644 src/urifilters/ikws/searchproviders/yahoo.desktop create mode 100644 src/urifilters/ikws/searchproviders/yahoo_image.desktop create mode 100644 src/urifilters/ikws/searchproviders/yahoo_local.desktop create mode 100644 src/urifilters/ikws/searchproviders/yahoo_shopping.desktop create mode 100644 src/urifilters/ikws/searchproviders/yahoo_video.desktop create mode 100644 src/urifilters/ikws/searchproviders/youtube.desktop create mode 100644 src/urifilters/localdomain/CMakeLists.txt create mode 100644 src/urifilters/localdomain/localdomainurifilter.cpp create mode 100644 src/urifilters/localdomain/localdomainurifilter.desktop create mode 100644 src/urifilters/localdomain/localdomainurifilter.h create mode 100644 src/urifilters/shorturi/CMakeLists.txt create mode 100644 src/urifilters/shorturi/kshorturifilter.cpp create mode 100644 src/urifilters/shorturi/kshorturifilter.desktop create mode 100644 src/urifilters/shorturi/kshorturifilter.h create mode 100644 src/urifilters/shorturi/kshorturifilterrc create mode 100644 src/widgets/CMakeLists.txt create mode 100644 src/widgets/accessmanager.cpp create mode 100644 src/widgets/accessmanager.h create mode 100644 src/widgets/accessmanagerreply_p.cpp create mode 100644 src/widgets/accessmanagerreply_p.h create mode 100644 src/widgets/certificateparty.ui create mode 100644 src/widgets/checksumswidget.ui create mode 100644 src/widgets/clipboardupdater.cpp create mode 100644 src/widgets/clipboardupdater_p.h create mode 100644 src/widgets/config-kiowidgets.h.cmake create mode 100644 src/widgets/delegateanimationhandler.cpp create mode 100644 src/widgets/delegateanimationhandler_p.h create mode 100644 src/widgets/dndpopupmenuplugin.cpp create mode 100644 src/widgets/dndpopupmenuplugin.h create mode 100644 src/widgets/dropjob.cpp create mode 100644 src/widgets/dropjob.h create mode 100644 src/widgets/executablefileopendialog.cpp create mode 100644 src/widgets/executablefileopendialog_p.h create mode 100644 src/widgets/fileundomanager.cpp create mode 100644 src/widgets/fileundomanager.h create mode 100644 src/widgets/fileundomanager_p.h create mode 100644 src/widgets/imagefilter.cpp create mode 100644 src/widgets/imagefilter_p.h create mode 100644 src/widgets/images/group-grey.png create mode 100644 src/widgets/images/group.png create mode 100644 src/widgets/images/mask.png create mode 100644 src/widgets/images/others-grey.png create mode 100644 src/widgets/images/others.png create mode 100644 src/widgets/images/user-grey.png create mode 100644 src/widgets/images/user.png create mode 100644 src/widgets/images/yes.png create mode 100644 src/widgets/images/yespartial.png create mode 100644 src/widgets/jobuidelegate.cpp create mode 100644 src/widgets/jobuidelegate.h create mode 100644 src/widgets/joburlcache.cpp create mode 100644 src/widgets/joburlcache_p.h create mode 100644 src/widgets/kabstractfileitemactionplugin.cpp create mode 100644 src/widgets/kabstractfileitemactionplugin.h create mode 100644 src/widgets/kacleditwidget.cpp create mode 100644 src/widgets/kacleditwidget.h create mode 100644 src/widgets/kacleditwidget.qrc create mode 100644 src/widgets/kacleditwidget_p.h create mode 100644 src/widgets/kautomount.cpp create mode 100644 src/widgets/kautomount.h create mode 100644 src/widgets/kbuildsycocaprogressdialog.cpp create mode 100644 src/widgets/kbuildsycocaprogressdialog.h create mode 100644 src/widgets/kdesktopfileactions.cpp create mode 100644 src/widgets/kdesktopfileactions.h create mode 100644 src/widgets/kdirlister.cpp create mode 100644 src/widgets/kdirlister.h create mode 100644 src/widgets/kdirmodel.cpp create mode 100644 src/widgets/kdirmodel.h create mode 100644 src/widgets/kdynamicjobtracker.cpp create mode 100644 src/widgets/kdynamicjobtracker_p.h create mode 100644 src/widgets/kfile.cpp create mode 100644 src/widgets/kfile.h create mode 100644 src/widgets/kfileitemactionplugin.desktop create mode 100644 src/widgets/kfileitemactions.cpp create mode 100644 src/widgets/kfileitemactions.h create mode 100644 src/widgets/kfileitemactions_p.h create mode 100644 src/widgets/kfileitemdelegate.cpp create mode 100644 src/widgets/kfileitemdelegate.h create mode 100644 src/widgets/kiodndpopupmenuplugin.desktop create mode 100644 src/widgets/konqpopupmenuplugin.desktop create mode 100644 src/widgets/kopenwithdialog.cpp create mode 100644 src/widgets/kopenwithdialog.h create mode 100644 src/widgets/kopenwithdialog_p.h create mode 100644 src/widgets/koverlayiconplugin.cpp create mode 100644 src/widgets/koverlayiconplugin.h create mode 100644 src/widgets/kpropertiesdesktopadvbase.ui create mode 100644 src/widgets/kpropertiesdesktopbase.ui create mode 100644 src/widgets/kpropertiesdialog.cpp create mode 100644 src/widgets/kpropertiesdialog.h create mode 100644 src/widgets/kpropertiesdialog_p.h create mode 100644 src/widgets/kpropertiesdialogplugin.desktop create mode 100644 src/widgets/krun.cpp create mode 100644 src/widgets/krun.h create mode 100644 src/widgets/krun_p.h create mode 100644 src/widgets/krun_win.cpp create mode 100644 src/widgets/kshellcompletion.cpp create mode 100644 src/widgets/kshellcompletion.h create mode 100644 src/widgets/ksslcertificatebox.cpp create mode 100644 src/widgets/ksslcertificatebox.h create mode 100644 src/widgets/ksslinfodialog.cpp create mode 100644 src/widgets/ksslinfodialog.h create mode 100644 src/widgets/kurifilter.cpp create mode 100644 src/widgets/kurifilter.h create mode 100644 src/widgets/kurifilterplugin.desktop create mode 100644 src/widgets/kurifiltersearchprovideractions.cpp create mode 100644 src/widgets/kurifiltersearchprovideractions.h create mode 100644 src/widgets/kurlcombobox.cpp create mode 100644 src/widgets/kurlcombobox.h create mode 100644 src/widgets/kurlcompletion.cpp create mode 100644 src/widgets/kurlcompletion.h create mode 100644 src/widgets/kurlpixmapprovider.cpp create mode 100644 src/widgets/kurlpixmapprovider.h create mode 100644 src/widgets/kurlrequester.cpp create mode 100644 src/widgets/kurlrequester.h create mode 100644 src/widgets/kurlrequesterdialog.cpp create mode 100644 src/widgets/kurlrequesterdialog.h create mode 100644 src/widgets/openfilemanagerwindowjob.cpp create mode 100644 src/widgets/openfilemanagerwindowjob.h create mode 100644 src/widgets/openfilemanagerwindowjob_p.h create mode 100644 src/widgets/org.kde.kio.FileUndoManager.xml create mode 100644 src/widgets/org.kde.kuiserver.xml create mode 100644 src/widgets/paste.cpp create mode 100644 src/widgets/paste.h create mode 100644 src/widgets/pastedialog.cpp create mode 100644 src/widgets/pastedialog_p.h create mode 100644 src/widgets/pastejob.cpp create mode 100644 src/widgets/pastejob.h create mode 100644 src/widgets/pastejob_p.h create mode 100644 src/widgets/pixmaploader.cpp create mode 100644 src/widgets/pixmaploader.h create mode 100644 src/widgets/previewjob.cpp create mode 100644 src/widgets/previewjob.h create mode 100644 src/widgets/renamedialog.cpp create mode 100644 src/widgets/renamedialog.h create mode 100644 src/widgets/skipdialog.cpp create mode 100644 src/widgets/skipdialog.h create mode 100644 src/widgets/sslinfo.ui create mode 100644 src/widgets/sslui.cpp create mode 100644 src/widgets/sslui.h create mode 100644 src/widgets/thumbcreator.cpp create mode 100644 src/widgets/thumbcreator.h create mode 100644 src/widgets/thumbsequencecreator.cpp create mode 100644 src/widgets/thumbsequencecreator.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/getalltest.cpp create mode 100644 tests/kdirlistertest_gui.cpp create mode 100644 tests/kdirlistertest_gui.h create mode 100644 tests/kdirmodeltest_gui.cpp create mode 100644 tests/kencodingfiledialogtest_gui.cpp create mode 100644 tests/kfilewidgettest_gui.cpp create mode 100644 tests/kionetrctest.cpp create mode 100644 tests/kioslavetest.cpp create mode 100644 tests/kioslavetest.h create mode 100644 tests/kmountpoint_debug.cpp create mode 100644 tests/kopenwithtest.cpp create mode 100644 tests/kpropertiesdialogtest.cpp create mode 100644 tests/kprotocolinfo_dumper.cpp create mode 100644 tests/kruntest.cpp create mode 100644 tests/kruntest.h create mode 100644 tests/ksycocaupdatetest.cpp create mode 100644 tests/kurlnavigatortest_gui.cpp create mode 100644 tests/kurlrequestertest_gui.cpp create mode 100644 tests/listjobtest.cpp create mode 100644 tests/listrecursivetest.cpp create mode 100644 tests/previewtest.cpp create mode 100644 tests/previewtest.h create mode 100644 tests/runapplication.cpp create mode 100644 tests/udsentrybenchmark.cpp diff --git a/.reviewboardrc b/.reviewboardrc new file mode 100644 index 0000000..550a34c --- /dev/null +++ b/.reviewboardrc @@ -0,0 +1,4 @@ +REVIEWBOARD_URL = "https://git.reviewboard.kde.org" +REPOSITORY = 'git://anongit.kde.org/kio' +BRANCH = 'master' +TARGET_GROUPS = 'kdeframeworks' diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d7efa84 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,135 @@ +cmake_minimum_required(VERSION 2.8.12) + +project(KIO) + +include(FeatureSummary) +find_package(ECM 5.28.0 NO_MODULE) +set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") +feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) + + +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake ) + +include(KDEInstallDirs) +include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) +include(KDECMakeSettings) + + +include(GenerateExportHeader) + +include(ECMMarkAsTest) +include(ECMSetupVersion) +include(ECMGenerateHeaders) +include(ECMMarkNonGuiExecutable) +include(ECMQtDeclareLoggingCategory) + +set(KF5_VERSION "5.28.0") # handled by release scripts +set(KF5_DEP_VERSION "5.28.0") # handled by release scripts + +ecm_setup_version( + ${KF5_VERSION} + VARIABLE_PREFIX KIO + VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/src/kio_version.h" + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5KIOConfigVersion.cmake" + SOVERSION 5) + +option(KIOCORE_ONLY "Only compile KIOCore, not KIOWidgets or anything that depends on it. This will disable support for cookies and passwordhandling (prompting and storing)." OFF) +option(KIO_FORK_SLAVES "If set we start the slaves via QProcess. It's also possible to change this by setting the environment variable KDE_FORK_SLAVES." OFF) + +find_package(KF5Archive ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5DBusAddons ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5Service ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5DocTools ${KF5_DEP_VERSION}) +find_package(KF5Solid ${KF5_DEP_VERSION} REQUIRED) # for kio_trash + +if (NOT KIOCORE_ONLY) +find_package(KF5Bookmarks ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5Completion ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5ConfigWidgets ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5IconThemes ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5ItemViews ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5JobWidgets ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5WidgetsAddons ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5WindowSystem ${KF5_DEP_VERSION} REQUIRED) +endif() + +# tell what is missing without doctools +set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Provides tools to generate documentation in various format from DocBook files" + TYPE OPTIONAL + PURPOSE "Required to build help ioslave and documentation" + ) + +# TODO: Remove these +remove_definitions(-DQT_NO_CAST_FROM_ASCII) +remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) + +add_definitions(-DQT_NO_URL_CAST_FROM_STRING) + +set(REQUIRED_QT_VERSION 5.5.0) +find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets DBus Network Concurrent Xml Test) + +find_package(GSSAPI) +set_package_properties(GSSAPI PROPERTIES DESCRIPTION "Allows KIO to make use of certain HTTP authentication services" + URL "http://web.mit.edu/kerberos/www" + TYPE OPTIONAL + PURPOSE "A MIT or HEIMDAL flavor of GSSAPI can be used" + ) + +if (NOT APPLE AND NOT WIN32) + find_package(X11) +endif() + +set(HAVE_X11 ${X11_FOUND}) +if (HAVE_X11) + find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED X11Extras) +endif() + +add_definitions(-DTRANSLATION_DOMAIN=\"kio5\") +if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") + ki18n_install(po) + + if (KF5DocTools_FOUND) + kdoctools_install(po) + endif() +endif() + +if (KF5DocTools_FOUND) + add_subdirectory(docs) +endif() + +include(CheckLibraryExists) +add_subdirectory(src) +add_subdirectory(autotests) + +if (NOT KIOCORE_ONLY) +add_subdirectory(tests) +endif() + +# create a Config.cmake and a ConfigVersion.cmake file and install them +set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5KIO") + +include(ECMPackageConfigHelpers) + +ecm_configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/KF5KIOConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/KF5KIOConfig.cmake" + PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/KF5KIOConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/KF5KIOConfigVersion.cmake" + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel +) + +install(EXPORT KF5KIOTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5KIOTargets.cmake NAMESPACE KF5:: ) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/src/kio_version.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) + +feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..2d2d780 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/KF5KIOConfig.cmake.in b/KF5KIOConfig.cmake.in new file mode 100644 index 0000000..0577c9a --- /dev/null +++ b/KF5KIOConfig.cmake.in @@ -0,0 +1,24 @@ +@PACKAGE_INIT@ + +# Any changes in this ".cmake" file will be overwritten by CMake, the source is the ".cmake.in" file. + +set(KCookieServer_DBUS_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/kf5_org.kde.KCookieServer.xml") +set(KSlaveLauncher_DBUS_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/kf5_org.kde.KSlaveLauncher.xml") + +find_dependency(KF5CoreAddons "@KF5_DEP_VERSION@") +find_dependency(KF5Config "@KF5_DEP_VERSION@") +find_dependency(KF5Service "@KF5_DEP_VERSION@") + +if (NOT @KIOCORE_ONLY@) +find_dependency(KF5Bookmarks "@KF5_DEP_VERSION@") +find_dependency(KF5Completion "@KF5_DEP_VERSION@") +find_dependency(KF5ItemViews "@KF5_DEP_VERSION@") +find_dependency(KF5JobWidgets "@KF5_DEP_VERSION@") +find_dependency(KF5Solid "@KF5_DEP_VERSION@") +find_dependency(KF5XmlGui "@KF5_DEP_VERSION@") +endif() + +find_dependency(Qt5Network "@REQUIRED_QT_VERSION@") + +include("${CMAKE_CURRENT_LIST_DIR}/KF5KIOTargets.cmake") + diff --git a/README.md b/README.md new file mode 100644 index 0000000..916430c --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# KIO + +Network transparent access to files and data + +## Introduction + +This framework implements almost all the file management functions you +will ever need. In fact, the KDE file manager (Dolphin) and the KDE +file dialog also uses this to provide its network-enabled file management. + +It supports accessing files locally as well as via HTTP and FTP out of the +box and can be extended by plugins to support other protocols as well. There +is a variety of plugins available, e.g. to support access via SSH. + +The framework can also be used to bridge a native protocol to a file-based +interface. This makes the data accessible in all applications using the KDE +file dialog or any other KIO enabled infrastructure. + diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt new file mode 100644 index 0000000..436b5d9 --- /dev/null +++ b/autotests/CMakeLists.txt @@ -0,0 +1,125 @@ +if(POLICY CMP0028) + cmake_policy(SET CMP0028 OLD) +endif() +remove_definitions(-DQT_NO_CAST_FROM_ASCII) + +include(ECMAddTests) + +add_subdirectory(http) +add_subdirectory(kcookiejar) + +find_package(Qt5Widgets REQUIRED) + +########### unittests ############### + +find_package(Qt5Concurrent ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) + +ecm_add_tests( + kacltest.cpp + listdirtest.cpp + kmountpointtest.cpp + upurltest.cpp + dataprotocoltest.cpp + jobtest.cpp + jobremotetest.cpp + kfileitemtest.cpp + kprotocolinfotest.cpp + ktcpsockettest.cpp + globaltest.cpp + mkpathjobtest.cpp + threadtest.cpp + udsentrytest.cpp + udsentry_benchmark.cpp + deletejobtest.cpp + NAME_PREFIX "kiocore-" + LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network +) + +target_link_libraries(threadtest Qt5::Concurrent) + +ecm_add_test( + http_jobtest.cpp + httpserver_p.cpp + TEST_NAME http_jobtest + NAME_PREFIX "kiocore-" + LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network +) + +if(UNIX) + ecm_add_tests( + klocalsockettest.cpp + klocalsocketservertest.cpp + NAME_PREFIX "kiocore-" + LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network + ) +endif() + +if (TARGET KF5::KIOGui) + ecm_add_tests( + favicontest.cpp + NAME_PREFIX "kiogui-" + LINK_LIBRARIES KF5::KIOCore KF5::KIOGui Qt5::Test + ) + + target_link_libraries(favicontest Qt5::Concurrent) +endif() + +if (TARGET KF5::KIOWidgets) +ecm_add_tests( + clipboardupdatertest.cpp + dropjobtest.cpp + krununittest.cpp + kdirlistertest.cpp + kdirmodeltest.cpp + kfileitemactionstest.cpp + fileundomanagertest.cpp + kurifiltertest.cpp + kurlcompletiontest.cpp + jobguitest.cpp + pastetest.cpp + accessmanagertest.cpp + kurifiltersearchprovideractionstest.cpp + NAME_PREFIX "kiowidgets-" + LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test +) +set_target_properties(krununittest PROPERTIES COMPILE_FLAGS "-DCMAKE_INSTALL_FULL_LIBEXECDIR_KF5=\\\"${CMAKE_INSTALL_FULL_LIBEXECDIR_KF5}\\\"") + +# Same as accessmanagertest, but using QNetworkAccessManager, to make sure we +# behave the same +ecm_add_test( + accessmanagertest.cpp + TEST_NAME accessmanagertest-qnam + NAME_PREFIX "kiowidgets-" + LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test +) +set_target_properties(accessmanagertest-qnam PROPERTIES COMPILE_FLAGS "-DUSE_QNAM") + +# Same as kurlcompletiontest, but with immediate return, and results posted by thread later +ecm_add_test( + kurlcompletiontest.cpp + TEST_NAME kurlcompletiontest-nowait + NAME_PREFIX "kiowidgets-" + LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test + ) +set_target_properties(kurlcompletiontest-nowait PROPERTIES COMPILE_FLAGS "-DNO_WAIT") + + +endif() + +if (TARGET KF5::KIOFileWidgets) +find_package(KF5XmlGui ${KF5_DEP_VERSION} REQUIRED) +include_directories(${CMAKE_SOURCE_DIR}/src/filewidgets ${CMAKE_BINARY_DIR}/src/filewidgets) +ecm_add_tests( + kurlnavigatortest.cpp + kurlcomboboxtest.cpp + kdiroperatortest.cpp + kfilewidgettest.cpp + knewfilemenutest.cpp + kfilecopytomenutest.cpp + kfileplacesmodeltest.cpp + kurlrequestertest.cpp + NAME_PREFIX "kiofilewidgets-" + LINK_LIBRARIES KF5::KIOFileWidgets KF5::KIOWidgets KF5::XmlGui KF5::Bookmarks Qt5::Test KF5::I18n +) +endif() + diff --git a/autotests/accessmanagertest.cpp b/autotests/accessmanagertest.cpp new file mode 100644 index 0000000..e84b1ba --- /dev/null +++ b/autotests/accessmanagertest.cpp @@ -0,0 +1,130 @@ +/* This file is part of the KDE libraries + Copyright (c) 2015 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include + +/** + * Unit test for AccessManager + */ +class AccessManagerTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase() + { + qputenv("KDE_FORK_SLAVES", "yes"); // To avoid a runtime dependency on klauncher + qputenv("KIOSLAVE_ENABLE_TESTMODE", "1"); // ensure the ioslaves call QStandardPaths::setTestModeEnabled too + QStandardPaths::setTestModeEnabled(true); + } + + void testGet() + { + const QString aFile = QFINDTESTDATA("accessmanagertest.cpp"); + QNetworkReply *reply = manager()->get(QNetworkRequest(QUrl::fromLocalFile(aFile))); + QSignalSpy spy(reply, SIGNAL(finished())); + QVERIFY(spy.wait()); + + QFile f(aFile); + QVERIFY(f.open(QIODevice::ReadOnly)); + QCOMPARE(f.readAll(), reply->readAll()); + } + + void testPut() + { +#if defined(USE_QNAM) && QT_VERSION <= QT_VERSION_CHECK(5, 5, 0) + QSKIP("This test is broken with Qt < 5.5, the fix is 9286a8e5dd97c5d4d7e0ed07a73d4ce7240fdc1d in qtbase"); +#endif + const QString aDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + QVERIFY(QDir::temp().mkpath(aDir)); + const QString aFile = aDir + QStringLiteral("/accessmanagertest-data"); + const QByteArray content = "We love free software!"; + QBuffer buffer; + buffer.setData(content); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + + QFile::remove(aFile); + + QNetworkReply *reply = manager()->put(QNetworkRequest(QUrl::fromLocalFile(aFile)), &buffer); + QSignalSpy spy(reply, SIGNAL(finished())); + QVERIFY(reply->isRunning()); + QVERIFY(spy.wait()); + + QVERIFY(QFile::exists(aFile)); + QFile f(aFile); + QVERIFY(f.open(QIODevice::ReadOnly)); + QCOMPARE(f.readAll(), content); + + QFile::remove(aFile); + } + + void testPutSequential() + { +#if defined(USE_QNAM) && QT_VERSION <= QT_VERSION_CHECK(5, 5, 0) + QSKIP("This test is broken with Qt < 5.5, the fix is 9286a8e5dd97c5d4d7e0ed07a73d4ce7240fdc1d in qtbase"); +#endif + const QString aDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + QVERIFY(QDir::temp().mkpath(aDir)); + const QString aFile = aDir + QStringLiteral("/accessmanagertest-data2"); + const QString putDataContents = "We love free software! " + QString(24000, 'c'); + QProcess process; + process.start(QStringLiteral("echo"), QStringList() << putDataContents); + + QFile::remove(aFile); + + QNetworkReply *reply = manager()->put(QNetworkRequest(QUrl::fromLocalFile(aFile)), &process); + QSignalSpy spy(reply, SIGNAL(finished())); + QVERIFY(spy.wait()); + QVERIFY(QFile::exists(aFile)); + + QFile f(aFile); + QVERIFY(f.open(QIODevice::ReadOnly)); + + QByteArray cts = f.readAll(); + cts.chop(1); //we remove the eof + QCOMPARE(QString::fromUtf8(cts).size(), putDataContents.size()); + QCOMPARE(QString::fromUtf8(cts), putDataContents); + + QFile::remove(aFile); + } + +private: + /** + * we want to run the tests both on QNAM and KIO::AccessManager + * to make sure they behave the same way. + */ + QNetworkAccessManager *manager() + { + static QNetworkAccessManager *ret = Q_NULLPTR; + if (!ret) { +#ifdef USE_QNAM + ret = new QNetworkAccessManager(this); +#else + ret = new KIO::AccessManager(this); +#endif + } + return ret; + } +}; + +QTEST_MAIN(AccessManagerTest) + +#include "accessmanagertest.moc" diff --git a/autotests/clipboardupdatertest.cpp b/autotests/clipboardupdatertest.cpp new file mode 100644 index 0000000..d5898de --- /dev/null +++ b/autotests/clipboardupdatertest.cpp @@ -0,0 +1,172 @@ +/* This file is part of KDE + Copyright (c) 2013 Dawit Alemayehu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include "clipboardupdatertest.h" +#include "kiotesthelper.h" +#include "clipboardupdater_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +QTEST_MAIN(ClipboardUpdaterTest) + +using namespace KIO; + +static QList tempFiles(const QTemporaryDir &dir, const QString &baseName, int count = 3) +{ + QList urls; + const QString path = dir.path(); + for (int i = 1; i < count + 1; ++i) { + const QString file = (path + '/' + baseName + QString::number(i)); + urls << QUrl::fromLocalFile(file); + createTestFile(file); + } + return urls; +} + +void ClipboardUpdaterTest::initTestCase() +{ + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); +} + +void ClipboardUpdaterTest::testPasteAfterRenameFiles() +{ + QTemporaryDir dir; + const QList urls = tempFiles(dir, QStringLiteral("rfile")); + + QClipboard *clipboard = QApplication::clipboard(); + QMimeData *mimeData = new QMimeData(); + mimeData->setUrls(urls); + clipboard->setMimeData(mimeData); + + Q_FOREACH (const QUrl &url, urls) { + QUrl newUrl = url; + newUrl.setPath(url.path() + QLatin1String("_renamed")); + KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); + QVERIFY(job->exec()); + } + + const QString pasteDir = dir.path() + QLatin1String("/pastedir"); + createTestDirectory(pasteDir, NoSymlink); + KIO::Job *job = KIO::paste(clipboard->mimeData(), QUrl::fromLocalFile(pasteDir)); + QVERIFY(job->exec()); + QCOMPARE(job->error(), 0); +} + +void ClipboardUpdaterTest::testPasteAfterMoveFile() +{ + QTemporaryDir dir; + const QList urls = tempFiles(dir, QStringLiteral("mfile"), 1); + + QClipboard *clipboard = QApplication::clipboard(); + QMimeData *mimeData = new QMimeData(); + mimeData->setUrls(urls); + clipboard->setMimeData(mimeData); + + const QString moveDir = dir.path() + QLatin1String("/movedir/"); + createTestDirectory(moveDir, NoSymlink); + const QUrl srcUrl = urls.first(); + QUrl destUrl = QUrl::fromLocalFile(moveDir); + destUrl = destUrl.adjusted(QUrl::RemoveFilename); + destUrl.setPath(destUrl.path() + srcUrl.fileName()); + KIO::FileCopyJob *mJob = KIO::file_move(srcUrl, destUrl, -1, KIO::HideProgressInfo); + QVERIFY(mJob->exec()); + + const QString pasteDir = dir.path() + QLatin1String("/pastedir"); + createTestDirectory(pasteDir, NoSymlink); + KIO::Job *job = KIO::paste(clipboard->mimeData(), QUrl::fromLocalFile(pasteDir)); + QVERIFY(job->exec()); + QCOMPARE(job->error(), 0); +} + +void ClipboardUpdaterTest::testPasteAfterMoveFiles() +{ + QTemporaryDir dir; + const QList urls = tempFiles(dir, QStringLiteral("mfile")); + + QClipboard *clipboard = QApplication::clipboard(); + QMimeData *mimeData = new QMimeData(); + mimeData->setUrls(urls); + clipboard->setMimeData(mimeData); + + const QString moveDir = dir.path() + QLatin1String("/movedir"); + createTestDirectory(moveDir, NoSymlink); + KIO::CopyJob *mJob = KIO::move(urls, QUrl::fromLocalFile(moveDir), KIO::HideProgressInfo); + QVERIFY(mJob->exec()); + + const QString pasteDir = dir.path() + QLatin1String("/pastedir"); + createTestDirectory(pasteDir, NoSymlink); + KIO::Job *job = KIO::pasteClipboard(QUrl::fromLocalFile(pasteDir), 0); + QVERIFY(job->exec()); + QCOMPARE(job->error(), 0); +} + +void ClipboardUpdaterTest::testPasteAfterDeleteFile() +{ + QTemporaryDir dir; + const QList urls = tempFiles(dir, QStringLiteral("dfile"), 1); + + QClipboard *clipboard = QApplication::clipboard(); + QMimeData *mimeData = new QMimeData(); + mimeData->setUrls(urls); + clipboard->setMimeData(mimeData); + + SimpleJob *sJob = KIO::file_delete(urls.first(), KIO::HideProgressInfo); + QVERIFY(sJob->exec()); + + QVERIFY(!clipboard->mimeData()->hasUrls()); + + const QString pasteDir = dir.path() + QLatin1String("/pastedir"); + createTestDirectory(pasteDir, NoSymlink); + KIO::Job *job = KIO::pasteClipboard(QUrl::fromLocalFile(pasteDir), 0); + QVERIFY(!job); +} + +void ClipboardUpdaterTest::testPasteAfterDeleteFiles() +{ + QTemporaryDir dir; + const QList urls = tempFiles(dir, QStringLiteral("dfile")); + + QClipboard *clipboard = QApplication::clipboard(); + QMimeData *mimeData = new QMimeData(); + mimeData->setUrls(urls); + clipboard->setMimeData(mimeData); + + DeleteJob *dJob = KIO::del(urls, KIO::HideProgressInfo); + QVERIFY(dJob->exec()); + + QVERIFY(!clipboard->mimeData()->hasUrls()); + + const QString pasteDir = dir.path() + QLatin1String("/pastedir"); + createTestDirectory(pasteDir, NoSymlink); + KIO::Job *job = KIO::pasteClipboard(QUrl::fromLocalFile(pasteDir), 0); + QVERIFY(!job); +} + diff --git a/autotests/clipboardupdatertest.h b/autotests/clipboardupdatertest.h new file mode 100644 index 0000000..c926965 --- /dev/null +++ b/autotests/clipboardupdatertest.h @@ -0,0 +1,38 @@ +/* This file is part of KDE + Copyright (c) 2013 Dawit Alemayehu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef CLIPBOARDUPDATETEST_H +#define CLIPBOARDUPDATETEST_H + +#include + +class ClipboardUpdaterTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testPasteAfterRenameFiles(); + void testPasteAfterMoveFile(); + void testPasteAfterMoveFiles(); + void testPasteAfterDeleteFile(); + void testPasteAfterDeleteFiles(); +}; + +#endif diff --git a/autotests/dataprotocoltest.cpp b/autotests/dataprotocoltest.cpp new file mode 100644 index 0000000..aa53182 --- /dev/null +++ b/autotests/dataprotocoltest.cpp @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2002,2003 Leo Savernik + * Copyright (C) 2012 Rolf Eike Beer + */ + +#ifdef DATAKIOSLAVE +# undef DATAKIOSLAVE +#endif +#ifndef TESTKIO +# define TESTKIO +#endif + +#include +#include "dataprotocoltest.h" +#include + +#include + +QTEST_MAIN(DataProtocolTest) + +class TestSlave +{ +public: + TestSlave() + { + } + virtual ~TestSlave() + { + } + + void mimeType(const QString &type) + { + QCOMPARE(type, mime_type_expected); + } + + void totalSize(KIO::filesize_t /*bytes*/) + { +// qDebug() << "content size: " << bytes << " bytes"; + } + + void setMetaData(const QString &key, const QString &value) + { + KIO::MetaData::Iterator it = attributes_expected.find(key); + QVERIFY(it != attributes_expected.end()); + QCOMPARE(value, it.value()); + attributes_expected.erase(it); + } + + void setAllMetaData(const KIO::MetaData &md) + { + KIO::MetaData::ConstIterator it = md.begin(); + KIO::MetaData::ConstIterator end = md.end(); + + for (; it != end; ++it) { + KIO::MetaData::Iterator eit = attributes_expected.find(it.key()); + QVERIFY(eit != attributes_expected.end()); + QCOMPARE(it.value(), eit.value()); + attributes_expected.erase(eit); + } + } + + void sendMetaData() + { + // check here if attributes_expected contains any excess keys + KIO::MetaData::ConstIterator it = attributes_expected.constBegin(); + KIO::MetaData::ConstIterator end = attributes_expected.constEnd(); + for (; it != end; ++it) { + qDebug() << "Metadata[\"" << it.key() + << "\"] was expected but not defined"; + } + QVERIFY(attributes_expected.isEmpty()); + } + + void data(const QByteArray &a) + { + if (a.isEmpty()) { +// qDebug() << ""; + } else { + QCOMPARE(a, content_expected); + }/*end if*/ + } + + void dispatch_data(const QByteArray &a) + { + data(a); + } + + void finished() + { + } + + void dispatch_finished() + { + } + + void ref() {} + void deref() {} + +private: + // -- testcase related members + QString mime_type_expected; // expected mime type + /** contains all attributes and values the testcase has to set */ + KIO::MetaData attributes_expected; + /** contains the content as it is expected to be returned */ + QByteArray content_expected; + +public: + /** + * sets the mime type that this testcase is expected to return + */ + void setExpectedMimeType(const QString &mime_type) + { + mime_type_expected = mime_type; + } + + /** + * sets all attribute-value pairs the testcase must deliver. + */ + void setExpectedAttributes(const KIO::MetaData &attres) + { + attributes_expected = attres; + } + + /** + * sets content as expected to be delivered by the testcase. + */ + void setExpectedContent(const QByteArray &content) + { + content_expected = content; + } +}; + +#include "dataprotocol.cpp" // we need access to static data & functions + +void runTest(const QByteArray &mimetype, const QStringList &metalist, const QByteArray &content, const QUrl &url) +{ + DataProtocol kio_data; + + kio_data.setExpectedMimeType(mimetype); + MetaData exp_attrs; + foreach (const QString &meta, metalist) { + const QStringList metadata = meta.split(QLatin1Char('=')); + Q_ASSERT(metadata.count() == 2); + exp_attrs[metadata[0]] = metadata[1]; + } + kio_data.setExpectedAttributes(exp_attrs); + kio_data.setExpectedContent(content); + + // check that mimetype(url) returns the same value as the complete parsing + kio_data.mimetype(url); + + kio_data.get(url); +} + +void DataProtocolTest::runAllTests() +{ + QFETCH(QByteArray, expected_mime_type); + QFETCH(QString, metadata); + QFETCH(QByteArray, exp_content); + QFETCH(QByteArray, url); + + const QStringList metalist = metadata.split(QLatin1Char('\n')); + + runTest(expected_mime_type, metalist, exp_content, QUrl(url)); +} + +void DataProtocolTest::runAllTests_data() +{ + QTest::addColumn("expected_mime_type"); + QTest::addColumn("metadata"); + QTest::addColumn("exp_content"); + QTest::addColumn("url"); + + const QByteArray textplain = "text/plain"; + const QString usascii = QStringLiteral("charset=us-ascii"); + const QString csutf8 = QStringLiteral("charset=utf-8"); + const QString cslatin1 = QStringLiteral("charset=iso-8859-1"); + const QString csiso7 = QStringLiteral("charset=iso-8859-7"); + + QTest::newRow("escape resolving") << + textplain << + usascii << + QByteArray("blah blah") << + QByteArray("data:,blah%20blah"); + + QTest::newRow("mime type, escape resolving") << + QByteArray("text/html") << + usascii << + QByteArray("
Rich text
") << + QByteArray("data:text/html,Rich%20text" + ""); + + QTest::newRow("whitespace test I") << + QByteArray("text/css") << + QStringLiteral("charset=iso-8859-15") << + QByteArray(" body { color: yellow; background:darkblue; font-weight:bold }") << + QByteArray("data:text/css ; charset = iso-8859-15 , body { color: yellow; " + "background:darkblue; font-weight:bold }"); + + QTest::newRow("out of spec argument order, base64 decoding, whitespace test II") << + textplain << + QStringLiteral("charset=iso-8859-1") << + QByteArray("paaaaaaaasd!!\n") << + QByteArray("data: ; base64 ; charset = \"iso-8859-1\" ,cGFhYWFhYWFhc2QhIQo="); + + QTest::newRow("arbitrary keys, reserved names as keys, whitespace test III") << + textplain << + QString::fromLatin1("base64=nospace\n" + "key=onespaceinner\n" + "key2=onespaceouter\n" + "charset=utf8\n" + "<>=") << + QByteArray("Die, Allied Schweinehund (C) 1990 Wolfenstein 3D") << + QByteArray("data: ;base64=nospace;key = onespaceinner; key2=onespaceouter ;" + " charset = utf8 ; <>= ,Die, Allied Schweinehund " + "(C) 1990 Wolfenstein 3D"); + + QTest::newRow("string literal with escaped chars, testing delimiters within string") << + textplain << + QStringLiteral("fortune-cookie=Master Leep say: \"Rabbit is humble, Rabbit is gentle; follow the Rabbit\"\ncharset=us-ascii") << + QByteArray("(C) 1997 Shadow Warrior ;-)") << + QByteArray("data:;fortune-cookie=\"Master Leep say: \\\"Rabbit is humble, " + "Rabbit is gentle; follow the Rabbit\\\"\",(C) 1997 Shadow Warrior " + ";-)"); + + QTest::newRow("escaped charset") << + textplain << + QStringLiteral("charset=iso-8859-7") << + QByteArray("test") << + QByteArray("data:text/plain;charset=%22%5cis%5co%5c-%5c8%5c8%5c5%5c9%5c-%5c7%22,test"); + + // the "greenbytes" tests are taken from http://greenbytes.de/tech/tc/datauri/ + QTest::newRow("greenbytes-simple") << + textplain << + usascii << + QByteArray("test") << + QByteArray("data:,test"); + + QTest::newRow("greenbytes-simplewfrag") << + textplain << + usascii << + QByteArray("test") << + QByteArray("data:,test#foo"); + + QTest::newRow("greenbytes-simple-utf8-dec") << + textplain << + csutf8 << + QByteArray("test \xc2\xa3 pound sign") << + QByteArray("data:text/plain;charset=utf-8,test%20%c2%a3%20pound%20sign"); + + QTest::newRow("greenbytes-simple-iso8859-1-dec") << + textplain << + cslatin1 << + QByteArray("test \xc2\xa3 pound sign") << + QByteArray("data:text/plain;charset=iso-8859-1,test%20%a3%20pound%20sign"); + + QTest::newRow("greenbytes-simple-iso8859-7-dec") << + textplain << + csiso7 << + QByteArray("test \xce\xa3 sigma") << + QByteArray("data:text/plain;charset=iso-8859-7,test%20%d3%20sigma"); + + QTest::newRow("greenbytes-simple-utf8-dec-dq") << + textplain << + csutf8 << + QByteArray("test \xc2\xa3 pound sign") << + QByteArray("data:text/plain;charset=%22utf-8%22,test%20%c2%a3%20pound%20sign"); + + QTest::newRow("greenbytes-simple-iso8859-1-dec-dq") << + textplain << + cslatin1 << + QByteArray("test \xc2\xa3 pound sign") << + QByteArray("data:text/plain;charset=%22iso-8859-1%22,test%20%a3%20pound%20sign"); + + QTest::newRow("greenbytes-simple-iso8859-7-dec-dq") << + textplain << + csiso7 << + QByteArray("test \xce\xa3 sigma") << + QByteArray("data:text/plain;charset=%22iso-8859-7%22,test%20%d3%20sigma"); + + QTest::newRow("greenbytes-simple-utf8-dec-dq-escaped") << + textplain << + csutf8 << + QByteArray("test \xc2\xa3 pound sign") << + QByteArray("data:text/plain;charset=%22%5cu%5ct%5cf%5c-%5c8%22,test%20%c2%a3%20pound%20sign"); + + QTest::newRow("greenbytes-simple-iso8859-1-dec-dq-escaped") << + textplain << + cslatin1 << + QByteArray("test \xc2\xa3 pound sign") << + QByteArray("data:text/plain;charset=%22%5ci%5cs%5co%5c-%5c8%5c8%5c5%5c9%5c-%5c1%22,test%20%a3%20pound%20sign"); + + QTest::newRow("greenbytes-simple-iso8859-7-dec-dq-escaped") << + textplain << + csiso7 << + QByteArray("test \xce\xa3 sigma") << + QByteArray("data:text/plain;charset=%22%5ci%5cs%5co%5c-%5c8%5c8%5c5%5c9%5c-%5c7%22,test%20%d3%20sigma"); + + QTest::newRow("greenbytes-simplefrag") << + QByteArray("text/html") << + usascii << + QByteArray("

foo

") << + QByteArray("data:text/html,%3Cp%3Efoo%3C%2Fp%3E#bar"); + + QTest::newRow("greenbytes-svg") << + QByteArray("image/svg+xml") << + usascii << + QByteArray("\n" + " \n" + "\n") << + QByteArray("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1" + "%22%3E%0A%20%20%3Ccircle%20cx%3D%22100%22%20cy%3D%22100%22%20r%3D%2225%22%20stroke%3D%22black%22%20" + "stroke-width%3D%221%22%20fill%3D%22green%22%2F%3E%0A%3C%2Fsvg%3E%0A#bar"); + + QTest::newRow("greenbytes-ext-simple") << + QByteArray("image/svg+xml") << + QStringLiteral("foo=bar\ncharset=us-ascii") << + QByteArray("\n" + " \n" + "\n") << + QByteArray("data:image/svg+xml;foo=bar,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1" + "%22%3E%0A%20%20%3Ccircle%20cx%3D%22100%22%20cy%3D%22100%22%20r%3D%2225%22%20stroke%3D%22black%22%20" + "stroke-width%3D%221%22%20fill%3D%22green%22%2F%3E%0A%3C%2Fsvg%3E%0A"); + + QTest::newRow("greenbytes-ext-simple-qs") << + QByteArray("image/svg+xml") << + QStringLiteral("foo=bar,bar\ncharset=us-ascii") << + QByteArray("\n" + " \n" + "\n") << + QByteArray("data:image/svg+xml;foo=%22bar,bar%22,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20" + "version%3D%221.1%22%3E%0A%20%20%3Ccircle%20cx%3D%22100%22%20cy%3D%22100%22%20r%3D%2225%22%20stroke%3D%22black" + "%22%20stroke-width%3D%221%22%20fill%3D%22green%22%2F%3E%0A%3C%2Fsvg%3E%0A"); +} + diff --git a/autotests/dataprotocoltest.h b/autotests/dataprotocoltest.h new file mode 100644 index 0000000..bddb78d --- /dev/null +++ b/autotests/dataprotocoltest.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2012 Rolf Eike Beer + */ + +#ifndef DATAPROTOCOLTEST_H +#define DATAPROTOCOLTEST_H + +#include + +#include +#include + +class DataProtocolTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void runAllTests(); + void runAllTests_data(); +}; + +#endif /* DATAPROTOCOLTEST_H */ diff --git a/autotests/deletejobtest.cpp b/autotests/deletejobtest.cpp new file mode 100644 index 0000000..b41ee9b --- /dev/null +++ b/autotests/deletejobtest.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015 Martin Blumenstingl + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "deletejobtest.h" + +#include +#include +#include +#include +#include +#include + +#include + +QTEST_MAIN(DeleteJobTest) + +#define KJOB_NO_ERROR static_cast(KJob::NoError) + +void DeleteJobTest::initTestCase() +{ + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); +} + +void DeleteJobTest::deleteFileTestCase_data() const +{ + QTest::addColumn("fileName"); + + QTest::newRow("latin characters") << "testfile"; + QTest::newRow("german umlauts") << "testger\u00E4t"; + QTest::newRow("chinese characters") << "\u8A66"; +} + +void DeleteJobTest::deleteFileTestCase() +{ + QFETCH(QString, fileName); + + QTemporaryFile tempFile; + tempFile.setFileTemplate(fileName + QStringLiteral("XXXXXX")); + + QVERIFY(tempFile.open()); + + /*QBENCHMARK*/ { + KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(tempFile.fileName()), KIO::HideProgressInfo); + job->setUiDelegate(0); + + QSignalSpy spy(job, SIGNAL(result(KJob*))); + QVERIFY(spy.isValid()); + QVERIFY(spy.wait(100000)); + QCOMPARE(job->error(), KJOB_NO_ERROR); + QVERIFY(!tempFile.exists()); + } +} + +void DeleteJobTest::deleteDirectoryTestCase_data() const +{ + QTest::addColumn("fileNames"); + + QStringList filesInNonEmptyDirectory = QStringList() << QStringLiteral("1.txt"); + QTest::newRow("non-empty directory") << filesInNonEmptyDirectory; + QTest::newRow("empty directory") << QStringList(); +} + +void DeleteJobTest::deleteDirectoryTestCase() +{ + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + QFETCH(QStringList, fileNames); + + createEmptyTestFiles(fileNames, tempDir.path()); + + /*QBENCHMARK*/ { + KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(tempDir.path()), KIO::HideProgressInfo); + job->setUiDelegate(0); + + QSignalSpy spy(job, SIGNAL(result(KJob*))); + QVERIFY(spy.isValid()); + QVERIFY(spy.wait(100000)); + QCOMPARE(job->error(), KJOB_NO_ERROR); + QVERIFY(!QDir(tempDir.path()).exists()); + } +} + +void DeleteJobTest::createEmptyTestFiles(const QStringList &fileNames, const QString &path) const +{ + QStringListIterator iterator(fileNames); + while (iterator.hasNext()) { + const QString filename = path + QDir::separator() + iterator.next(); + QFile file(filename); + QVERIFY(file.open(QIODevice::WriteOnly)); + } + + QCOMPARE(QDir(path).entryList(QDir::Files).count(), fileNames.size()); +} diff --git a/autotests/deletejobtest.h b/autotests/deletejobtest.h new file mode 100644 index 0000000..b39e160 --- /dev/null +++ b/autotests/deletejobtest.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 Martin Blumenstingl + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef DELETEJOBTEST_H +#define DELETEJOBTEST_H + +#include +#include + +class DeleteJobTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void deleteFileTestCase_data() const; + void deleteFileTestCase(); + void deleteDirectoryTestCase_data() const; + void deleteDirectoryTestCase(); + +private: + void createEmptyTestFiles(const QStringList &fileNames, const QString &path) const; +}; + +#endif diff --git a/autotests/dropjobtest.cpp b/autotests/dropjobtest.cpp new file mode 100644 index 0000000..ad83029 --- /dev/null +++ b/autotests/dropjobtest.cpp @@ -0,0 +1,488 @@ +/* This file is part of the KDE project + Copyright (C) 2014 David Faure + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "kiotesthelper.h" + +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(Qt::KeyboardModifiers) +Q_DECLARE_METATYPE(Qt::DropAction) +Q_DECLARE_METATYPE(Qt::DropActions) +Q_DECLARE_METATYPE(KFileItemListProperties) + +#ifndef Q_OS_WIN +void initLocale() +{ + setenv("LC_ALL", "en_US.utf-8", 1); +} +Q_CONSTRUCTOR_FUNCTION(initLocale) +#endif + +class JobSpy : public QObject +{ + Q_OBJECT +public: + JobSpy(KIO::Job *job) + : QObject(0), + m_spy(job, SIGNAL(result(KJob*))), + m_error(0) + { + connect(job, &KJob::result, this, [this](KJob * job) { + m_error = job->error(); + }); + } + // like job->exec(), but with a timeout (to avoid being stuck with a popup grabbing mouse and keyboard...) + bool waitForResult() + { + // implementation taken from QTRY_COMPARE, to move the QVERIFY to the caller + if (m_spy.isEmpty()) { + QTest::qWait(0); + } + for (int i = 0; i < 5000 && m_spy.isEmpty(); i += 50) { + QTest::qWait(50); + } + return !m_spy.isEmpty(); + } + int error() const + { + return m_error; + } + +private: + QSignalSpy m_spy; + int m_error; +}; + +class DropJobTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase() + { + QStandardPaths::setTestModeEnabled(true); + qputenv("KIOSLAVE_ENABLE_TESTMODE", "1"); // ensure the ioslaves call QStandardPaths::setTestModeEnabled too + + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + + const QString trashDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/Trash"); + QDir(trashDir).removeRecursively(); + + QVERIFY(m_tempDir.isValid()); + QVERIFY(m_nonWritableTempDir.isValid()); + QVERIFY(QFile(m_nonWritableTempDir.path()).setPermissions(QFile::ReadOwner | QFile::ReadUser | QFile::ExeOwner | QFile::ExeUser)); + m_srcDir = m_tempDir.path(); + + m_srcFile = m_srcDir + "/srcfile"; + m_srcLink = m_srcDir + "/link"; + } + + void cleanupTestCase() + { + QVERIFY(QFile(m_nonWritableTempDir.path()).setPermissions(QFile::ReadOwner | QFile::ReadUser | QFile::WriteOwner | QFile::WriteUser | QFile::ExeOwner | QFile::ExeUser)); + } + + // Before every test method, ensure the test file m_srcFile exists + void init() + { + if (QFile::exists(m_srcFile)) { + QVERIFY(QFileInfo(m_srcFile).isWritable()); + } else { + QFile srcFile(m_srcFile); + QVERIFY2(srcFile.open(QFile::WriteOnly), qPrintable(srcFile.errorString())); + srcFile.write("Hello world\n"); + } +#ifndef Q_OS_WIN + if (!QFile::exists(m_srcLink)) { + QVERIFY(QFile(m_srcFile).link(m_srcLink)); + QVERIFY(QFileInfo(m_srcLink).isSymLink()); + } +#endif + QVERIFY(QFileInfo(m_srcFile).isWritable()); + m_mimeData.setUrls(QList() << QUrl::fromLocalFile(m_srcFile)); + } + + void shouldDropToDesktopFile() + { + // Given an executable application desktop file and a source file + const QString desktopPath = m_srcDir + "/target.desktop"; + KDesktopFile desktopFile(desktopPath); + KConfigGroup desktopGroup = desktopFile.desktopGroup(); + desktopGroup.writeEntry("Type", "Application"); + desktopGroup.writeEntry("StartupNotify", "false"); +#ifdef Q_OS_WIN + desktopGroup.writeEntry("Exec", "copy.exe %f %d/dest"); +#else + desktopGroup.writeEntry("Exec", "cp %f %d/dest"); +#endif + desktopFile.sync(); + QFile file(desktopPath); + file.setPermissions(file.permissions() | QFile::ExeOwner | QFile::ExeUser); + + // When dropping the source file onto the desktop file + QUrl destUrl = QUrl::fromLocalFile(desktopPath); + QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction, &m_mimeData, Qt::LeftButton, Qt::NoModifier); + KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(itemCreated(QUrl))); + + // Then the application is run with the source file as argument + // (in this example, it copies the source file to "dest") + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(spy.count(), 0); + const QString dest = m_srcDir + "/dest"; + QTRY_VERIFY(QFile::exists(dest)); + + QVERIFY(QFile::remove(desktopPath)); + QVERIFY(QFile::remove(dest)); + } + + void shouldDropToDirectory_data() + { + QTest::addColumn("modifiers"); + QTest::addColumn("dropAction"); // Qt's dnd support sets it from the modifiers, we fake it here + QTest::addColumn("srcFile"); + QTest::addColumn("dest"); // empty for a temp dir + QTest::addColumn("expectedError"); + QTest::addColumn("shouldSourceStillExist"); + + QTest::newRow("Ctrl") << Qt::KeyboardModifiers(Qt::ControlModifier) << Qt::CopyAction << m_srcFile << QString() + << 0 << true; + QTest::newRow("Shift") << Qt::KeyboardModifiers(Qt::ShiftModifier) << Qt::MoveAction << m_srcFile << QString() + << 0 << false; + QTest::newRow("Ctrl_Shift") << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << Qt::LinkAction << m_srcFile << QString() + << 0 << true; + QTest::newRow("DropOnItself") << Qt::KeyboardModifiers() << Qt::CopyAction << m_srcDir << m_srcDir + << int(KIO::ERR_DROP_ON_ITSELF) << true; + QTest::newRow("DropDirOnFile") << Qt::KeyboardModifiers(Qt::ControlModifier) << Qt::CopyAction << m_srcDir << m_srcFile + << int(KIO::ERR_ACCESS_DENIED) << true; + QTest::newRow("NonWritableDest") << Qt::KeyboardModifiers() << Qt::CopyAction << m_srcFile << m_nonWritableTempDir.path() + << int(KIO::ERR_WRITE_ACCESS_DENIED) << true; + } + + void shouldDropToDirectory() + { + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(Qt::DropAction, dropAction); + QFETCH(QString, srcFile); + QFETCH(QString, dest); + QFETCH(int, expectedError); + QFETCH(bool, shouldSourceStillExist); + + // Given a directory and a source file + QTemporaryDir tempDestDir; + QVERIFY(tempDestDir.isValid()); + if (dest.isEmpty()) { + dest = tempDestDir.path(); + } + + // When dropping the source file onto the directory + const QUrl destUrl = QUrl::fromLocalFile(dest); + m_mimeData.setUrls(QList() << QUrl::fromLocalFile(srcFile)); + QDropEvent dropEvent(QPoint(10, 10), dropAction, &m_mimeData, Qt::LeftButton, modifiers); + KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + JobSpy jobSpy(job); + QSignalSpy spy(job, SIGNAL(itemCreated(QUrl))); + + // Then the file is copied + QVERIFY(jobSpy.waitForResult()); + QCOMPARE(jobSpy.error(), expectedError); + if (expectedError == 0) { + const QString destFile = dest + "/srcfile"; + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.at(0).at(0).value(), QUrl::fromLocalFile(destFile)); + QVERIFY(QFile::exists(destFile)); + QCOMPARE(QFile::exists(m_srcFile), shouldSourceStillExist); + if (dropAction == Qt::LinkAction) { + QVERIFY(QFileInfo(destFile).isSymLink()); + } + } + } + + void shouldDropToTrash_data() + { + QTest::addColumn("modifiers"); + QTest::addColumn("dropAction"); // Qt's dnd support sets it from the modifiers, we fake it here + QTest::addColumn("srcFile"); + + QTest::newRow("Ctrl") << Qt::KeyboardModifiers(Qt::ControlModifier) << Qt::CopyAction << m_srcFile; + QTest::newRow("Shift") << Qt::KeyboardModifiers(Qt::ShiftModifier) << Qt::MoveAction << m_srcFile; + QTest::newRow("Ctrl_Shift") << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << Qt::LinkAction << m_srcFile; + QTest::newRow("NoModifiers") << Qt::KeyboardModifiers() << Qt::CopyAction << m_srcFile; +#ifndef Q_OS_WIN + QTest::newRow("Link_Ctrl") << Qt::KeyboardModifiers(Qt::ControlModifier) << Qt::CopyAction << m_srcLink; + QTest::newRow("Link_Shift") << Qt::KeyboardModifiers(Qt::ShiftModifier) << Qt::MoveAction << m_srcLink; + QTest::newRow("Link_Ctrl_Shift") << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << Qt::LinkAction << m_srcLink; + QTest::newRow("Link_NoModifiers") << Qt::KeyboardModifiers() << Qt::CopyAction << m_srcLink; +#endif + } + + void shouldDropToTrash() + { + // Given a source file + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(Qt::DropAction, dropAction); + QFETCH(QString, srcFile); + const bool isLink = QFileInfo(srcFile).isSymLink(); + + // When dropping it into the trash, with pressed + m_mimeData.setUrls(QList() << QUrl::fromLocalFile(srcFile)); + QDropEvent dropEvent(QPoint(10, 10), dropAction, &m_mimeData, Qt::LeftButton, modifiers); + KIO::DropJob *job = KIO::drop(&dropEvent, QUrl(QStringLiteral("trash:/")), KIO::HideProgressInfo); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(itemCreated(QUrl))); + + // Then a confirmation dialog should appear + PredefinedAnswerJobUiDelegate extension; + extension.m_deleteResult = true; + job->setUiDelegateExtension(&extension); + + // and the file should be moved to the trash, no matter what the modifiers are + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(extension.m_askDeleteCalled, 1); + QCOMPARE(spy.count(), 1); + const QUrl trashUrl = spy.at(0).at(0).value(); + QCOMPARE(trashUrl.scheme(), QString("trash")); + KIO::StatJob *statJob = KIO::stat(trashUrl, KIO::HideProgressInfo); + QVERIFY(statJob->exec()); + if (isLink) { + QVERIFY(statJob->statResult().isLink()); + } + + // clean up + KIO::DeleteJob *delJob = KIO::del(trashUrl, KIO::HideProgressInfo); + QVERIFY2(delJob->exec(), qPrintable(delJob->errorString())); + } + + void shouldDropFromTrash() + { + // Given a file in the trash + const QFile::Permissions origPerms = QFileInfo(m_srcFile).permissions(); + QVERIFY(QFileInfo(m_srcFile).isWritable()); + KIO::CopyJob *copyJob = KIO::move(QUrl::fromLocalFile(m_srcFile), QUrl(QStringLiteral("trash:/"))); + QSignalSpy copyingDoneSpy(copyJob, SIGNAL(copyingDone(KIO::Job*,QUrl,QUrl,QDateTime,bool,bool))); + QVERIFY(copyJob->exec()); + const QUrl trashUrl = copyingDoneSpy.at(0).at(2).value(); + QVERIFY(trashUrl.isValid()); + QVERIFY(!QFile::exists(m_srcFile)); + + // When dropping the trashed file into a local dir, without modifiers + m_mimeData.setUrls(QList() << trashUrl); + QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction, &m_mimeData, Qt::LeftButton, Qt::NoModifier); + KIO::DropJob *job = KIO::drop(&dropEvent, QUrl::fromLocalFile(m_srcDir), KIO::HideProgressInfo); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(itemCreated(QUrl))); + + // Then the file should be moved, without a popup. No point in copying out of the trash, or linking to it. + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.at(0).at(0).value(), QUrl::fromLocalFile(m_srcFile)); + QVERIFY(QFile::exists(m_srcFile)); + QCOMPARE(int(QFileInfo(m_srcFile).permissions()), int(origPerms)); + QVERIFY(QFileInfo(m_srcFile).isWritable()); + KIO::StatJob *statJob = KIO::stat(trashUrl, KIO::HideProgressInfo); + QVERIFY(!statJob->exec()); + QVERIFY(QFileInfo(m_srcFile).isWritable()); + } + + void shouldDropTrashRootWithoutMovingAllTrashedFiles() // #319660 + { + // Given some stuff in the trash + const QUrl trashUrl(QStringLiteral("trash:/")); + KIO::CopyJob *copyJob = KIO::move(QUrl::fromLocalFile(m_srcFile), trashUrl); + QVERIFY(copyJob->exec()); + // and an empty destination directory + QTemporaryDir tempDestDir; + QVERIFY(tempDestDir.isValid()); + const QUrl destUrl = QUrl::fromLocalFile(tempDestDir.path()); + + // When dropping a link / icon of the trash... + m_mimeData.setUrls(QList() << trashUrl); + QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction, &m_mimeData, Qt::LeftButton, Qt::NoModifier); + KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo); + job->setUiDelegate(0); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + + // Then a full move shouldn't happen, just a link + const QStringList items = QDir(tempDestDir.path()).entryList(); + QVERIFY2(!items.contains("srcfile"), qPrintable(items.join(','))); + QVERIFY2(items.contains("trash:" + QChar(0x2044) + ".desktop"), qPrintable(items.join(','))); + } + + void shouldDropToDirectoryWithPopup_data() + { + QTest::addColumn("dest"); // empty for a temp dir + QTest::addColumn("offeredActions"); + QTest::addColumn("triggerActionNumber"); + QTest::addColumn("expectedError"); + QTest::addColumn("expectedDropAction"); + QTest::addColumn("shouldSourceStillExist"); + + const Qt::DropActions threeActions = Qt::MoveAction | Qt::CopyAction | Qt::LinkAction; + const Qt::DropActions copyAndLink = Qt::CopyAction | Qt::LinkAction; + QTest::newRow("Move") << QString() << threeActions << 0 << 0 << Qt::MoveAction << false; + QTest::newRow("Copy") << QString() << threeActions << 1 << 0 << Qt::CopyAction << true; + QTest::newRow("Link") << QString() << threeActions << 2 << 0 << Qt::LinkAction << true; + QTest::newRow("SameDestCopy") << m_srcDir << copyAndLink << 0 << int(KIO::ERR_IDENTICAL_FILES) << Qt::CopyAction << true; + QTest::newRow("SameDestLink") << m_srcDir << copyAndLink << 1 << int(KIO::ERR_FILE_ALREADY_EXIST) << Qt::LinkAction << true; + } + + void shouldDropToDirectoryWithPopup() + { + QFETCH(QString, dest); + QFETCH(Qt::DropActions, offeredActions); + QFETCH(int, triggerActionNumber); + QFETCH(int, expectedError); + QFETCH(Qt::DropAction, expectedDropAction); + QFETCH(bool, shouldSourceStillExist); + + // Given a directory and a source file + QTemporaryDir tempDestDir; + QVERIFY(tempDestDir.isValid()); + if (dest.isEmpty()) { + dest = tempDestDir.path(); + } + QVERIFY(!findPopup()); + + // When dropping the source file onto the directory + QUrl destUrl = QUrl::fromLocalFile(dest); + QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction /*unused*/, &m_mimeData, Qt::LeftButton, Qt::NoModifier); + KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); // no rename dialog + JobSpy jobSpy(job); + qRegisterMetaType(); + QSignalSpy spyShow(job, SIGNAL(popupMenuAboutToShow(KFileItemListProperties))); + QVERIFY(spyShow.isValid()); + + // Then a popup should appear, with the expected available actions + QVERIFY(spyShow.wait()); + QTRY_VERIFY(findPopup()); + QMenu *popup = findPopup(); + QCOMPARE(int(popupDropActions(popup)), int(offeredActions)); + + // And when selecting action number + QAction *action = popup->actions().at(triggerActionNumber); + QVERIFY(action); + QCOMPARE(int(action->data().value()), int(expectedDropAction)); + const QRect actionGeom = popup->actionGeometry(action); + QTest::mouseClick(popup, Qt::LeftButton, Qt::NoModifier, actionGeom.center()); + + // Then the job should finish, and the chosen action should happen. + QVERIFY(jobSpy.waitForResult()); + QCOMPARE(jobSpy.error(), expectedError); + if (expectedError == 0) { + const QString destFile = dest + "/srcfile"; + QVERIFY(QFile::exists(destFile)); + QCOMPARE(QFile::exists(m_srcFile), shouldSourceStillExist); + if (expectedDropAction == Qt::LinkAction) { + QVERIFY(QFileInfo(destFile).isSymLink()); + } + } + QTRY_VERIFY(!findPopup()); // flush deferred delete, so we don't get this popup again in findPopup + } + + void shouldAddApplicationActionsToPopup() + { + // Given a directory and a source file + QTemporaryDir tempDestDir; + QVERIFY(tempDestDir.isValid()); + const QUrl destUrl = QUrl::fromLocalFile(tempDestDir.path()); + + // When dropping the source file onto the directory + QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction /*unused*/, &m_mimeData, Qt::LeftButton, Qt::NoModifier); + KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo); + QAction appAction1(QStringLiteral("action1"), this); + QAction appAction2(QStringLiteral("action2"), this); + QList appActions; appActions << &appAction1 << &appAction2; + job->setUiDelegate(0); + job->setApplicationActions(appActions); + JobSpy jobSpy(job); + + // Then a popup should appear, with the expected available actions + QTRY_VERIFY(findPopup()); + QMenu *popup = findPopup(); + const QList actions = popup->actions(); + QVERIFY(actions.contains(&appAction1)); + QVERIFY(actions.contains(&appAction2)); + QVERIFY(actions.at(actions.indexOf(&appAction1) - 1)->isSeparator()); + QVERIFY(actions.at(actions.indexOf(&appAction2) + 1)->isSeparator()); + + // And when selecting action appAction1 + const QRect actionGeom = popup->actionGeometry(&appAction1); + QTest::mouseClick(popup, Qt::LeftButton, Qt::NoModifier, actionGeom.center()); + + // Then the menu should hide and the job terminate (without doing any copying) + QVERIFY(jobSpy.waitForResult()); + QCOMPARE(jobSpy.error(), 0); + const QString destFile = tempDestDir.path() + "/srcfile"; + QVERIFY(!QFile::exists(destFile)); + } + +private: + static QMenu *findPopup() + { + Q_FOREACH (QWidget *widget, qApp->topLevelWidgets()) { + if (QMenu *menu = qobject_cast(widget)) { + return menu; + } + } + return Q_NULLPTR; + } + static Qt::DropActions popupDropActions(QMenu *menu) + { + Qt::DropActions actions; + Q_FOREACH (QAction *action, menu->actions()) { + const QVariant userData = action->data(); + if (userData.isValid()) { + actions |= userData.value(); + } + } + return actions; + } + QMimeData m_mimeData; // contains m_srcFile + QTemporaryDir m_tempDir; + QString m_srcDir; + QString m_srcFile; + QString m_srcLink; + QTemporaryDir m_nonWritableTempDir; +}; + +QTEST_MAIN(DropJobTest) + +#include "dropjobtest.moc" + diff --git a/autotests/fakecomputer.xml b/autotests/fakecomputer.xml new file mode 100644 index 0000000..48be046 --- /dev/null +++ b/autotests/fakecomputer.xml @@ -0,0 +1,387 @@ + + + + + + Computer + Solid + + + + + Solid Processor #0 + Processor + Acme Corporation + /org/kde/solid/fakehw/computer + 0 + 3200 + true + mmx,sse + + + Solid Processor #1 + Processor + Acme Corporation + /org/kde/solid/fakehw/computer + 1 + 3200 + true + + + + + Platform Device (floppy) + /org/kde/solid/fakehw/computer + + + + PC Floppy Drive + StorageDrive,Block + /org/kde/solid/fakehw/platform_floppy_0 + + 0 + 2 + /dev/fd0 + + platform + floppy + true + false + false + false + + + + Floppy Disk + Block,StorageVolume,StorageAccess + /org/kde/solid/fakehw/platform_floppy_0_storage + + 0 + 2 + /dev/fd0 + + false + true + /media/floppy0 + filesystem + + + + + + + 99021 IDE Controller #1 + Acme Corporation + /org/kde/solid/fakehw/computer + + + + + IDE device (master) + /org/kde/solid/fakehw/pci_001 + + + + HD250GB + Acme Corporation + StorageDrive,Block + /org/kde/solid/fakehw/pci_001_ide_0_0 + + 0 + 3 + /dev/hda + + scsi + disk + false + false + false + false + HD250GBSATA + + + + / + Block,StorageVolume,StorageAccess + /org/kde/solid/fakehw/storage_serial_HD56890I + + 1 + 3 + /dev/hda1 + + true + true + / + filesystem + ext3 + Root + feedface + 21474836480 + + + /home + Block,StorageVolume,StorageAccess + /org/kde/solid/fakehw/storage_serial_HD56890I + + 6 + 3 + /dev/hda6 + + true + true + /home + filesystem + xfs + Home + c0ffee + 223338299392 + + + /foreign + Block,StorageVolume,StorageAccess + /org/kde/solid/fakehw/storage_serial_HD56890I + + 7 + 3 + /dev/hda7 + + false + true + /foreign + filesystem + ntfs + Foreign + f00ba7 + 21474836480 + + + StorageVolume + Block,StorageVolume,StorageAccess + /org/kde/solid/fakehw/storage_serial_HD56890I + + 2 + 3 + /dev/hda2 + + true + false + other + 1024 + + + StorageVolume (swap) + Block,StorageVolume,StorageAccess + /org/kde/solid/fakehw/storage_serial_HD56890I + + 5 + 3 + /dev/hda5 + + true + false + other + swap + 2147483648 + + + + + + 99021 IDE Controller #2 + Acme Corporation + /org/kde/solid/fakehw/computer + + + + + IDE device (master) + /org/kde/solid/fakehw/pci_002 + + + + Solid IDE DVD Writer + Acme Corporation + Block,StorageDrive,OpticalDrive + /org/kde/solid/fakehw/pci_002_ide_1_0 + + 0 + 22 + /dev/hdc + + ide + cdrom + true + true + false + true + Solid DVD Writer + + cdr,cdrw,dvd,dvdr,dvdrw + 4234 + 4234 + 4234,2822,2117,1411,706 + + + + FooDistro i386 + Block,StorageVolume,OpticalDisc,StorageAccess + /org/kde/solid/fakehw/storage_model_solid_writer + + cd_rw + false + true + false + data + /media/cdrom + + 5011 + 731047936 + FooDistro i386 + + + + + IDE device (slave) + /org/kde/solid/fakehw/pci_002 + + + + Solid IDE DVD Reader + Acme Corporation + Block,StorageDrive,OpticalDrive + /org/kde/solid/fakehw/pci_002_ide_1_1 + + 0 + 22 + /dev/hdc + + ide + cdrom + true + true + false + true + Solid DVD Reader + + cdr,cdrw,dvd,dvdr,dvdrw,dvdram,dvdplusr,dvdplusrw + 4234 + + + + SolidMan Begins + Block,StorageVolume,OpticalDisc + /org/kde/solid/fakehw/storage_model_solid_reader + + dvd_rom + false + false + false + dvdvideo + + 5012 + 8033075200 + SolidMan Begins + + + + + + + 99021 USB2 EHCI Controller #1 + Acme Corporation + /org/kde/solid/fakehw/computer + + + + EHCI Host Controller + Kernel ehci_hcd + /org/kde/solid/fakehw/pci_8086_265c + + + + Acme XO-Y4 + /org/kde/solid/fakehw/usb_device_0_0_1d_7 + + + + USB Mass Storage Inferface + /org/kde/solid/fakehw/usb_device_4e8_5041 + + + + SCSI Host Adapter + /org/kde/solid/fakehw/usb_device_4e8_5041_if0 + + + + SCSI Device + /org/kde/solid/fakehw/usb_device_4e8_5041_if0_scsi_host + + + + XO-Y4 + Acme Electronics + StorageDrive,Block,PortableMediaPlayer + /org/kde/solid/fakehw/usb_device_4e8_5041_if0_scsi_host_scsi_device_lun0 + + 0 + 8 + /dev/sda + + usb + disk + true + true + true + true + XO-Y4 + + MassStorage + audio/x-mp3 + audio/x-wav,audio/x-mp3,audio/vorbis + audio/x-mpegurl + + + + StorageVolume (vfat) + Block,StorageVolume,StorageAccess + /org/kde/solid/fakehw/storage_serial_XOY4_5206 + + 1 + 8 + /dev/sda1 + + false + true + /media/XO-Y4 + filesystem + vfat + 993284096 + + + + Network Shares + Network Shares + KDE + /org/kde/solid/fakehw/computer + + + /org/kde/solid/fstab + NetworkShare,StorageAccess + /solidpath + thehost + nfs + nfs://thehost/solid-path + /media/nfs + false + true + /media/nfs + + diff --git a/autotests/favicontest.cpp b/autotests/favicontest.cpp new file mode 100644 index 0000000..0959023 --- /dev/null +++ b/autotests/favicontest.cpp @@ -0,0 +1,303 @@ +/* This file is part of KDE + Copyright (c) 2006-2016 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static const char s_hostUrl[] = "http://www.google.com/index.html"; +static const char s_pageUrl[] = "http://www.google.com/somepage.html"; +static const char s_iconUrl[] = "http://www.google.com/favicon.ico"; +static const char s_altIconUrl[] = "http://www.ibm.com/favicon.ico"; +static const char s_thirdIconUrl[] = "http://www.google.fr/favicon.ico"; +static const char s_iconUrlForThreadTest[] = "http://www.google.de/favicon.ico"; + +static enum NetworkAccess { Unknown, Yes, No } s_networkAccess = Unknown; +static bool checkNetworkAccess() +{ + if (s_networkAccess == Unknown) { + QElapsedTimer tm; + tm.start(); + KIO::Job *job = KIO::get(QUrl(s_iconUrl), KIO::NoReload, KIO::HideProgressInfo); + if (job->exec()) { + s_networkAccess = Yes; + qDebug() << "Network access OK. Download time" << tm.elapsed() << "ms"; + } else { + qWarning() << job->errorString(); + s_networkAccess = No; + } + } + return s_networkAccess == Yes; +} + +class FavIconTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void favIconForUrlShouldBeEmptyInitially(); + void hostJobShouldDownloadIconThenUseCache(); + void iconUrlJobShouldDownloadIconThenUseCache(); + void reloadShouldReload(); + void failedDownloadShouldBeRemembered(); + void tooBigFaviconShouldAbort(); + void simultaneousRequestsShouldWork(); + void concurrentRequestsShouldWork(); + +private: +}; + +void FavIconTest::initTestCase() +{ + QStandardPaths::setTestModeEnabled(true); + + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + // To let ctest exit, we shouldn't start kio_http_cache_cleaner + qputenv("KIO_DISABLE_CACHE_CLEANER", "yes"); + // To get KJob::errorString() in English + qputenv("LC_ALL", "en_US.UTF-8"); + + if (!checkNetworkAccess()) { + QSKIP("no network access", SkipAll); + } + + // Ensure we start with no cache on disk + const QString favIconCacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/favicons"); + QDir(favIconCacheDir).removeRecursively(); + QVERIFY(!QFileInfo::exists(favIconCacheDir)); + + // Enable debug output + QLoggingCategory::setFilterRules(QStringLiteral("kde.kio.favicons.debug=true")); +} + +void FavIconTest::favIconForUrlShouldBeEmptyInitially() +{ + QCOMPARE(KIO::favIconForUrl(QUrl(s_hostUrl)), QString()); +} + +// Waits for start() and checks whether a transfer job was created. +static bool willDownload(KIO::FavIconRequestJob *job) +{ + qApp->sendPostedEvents(job, QEvent::MetaCall); // start() is delayed + return job->findChild(); +} + +void FavIconTest::hostJobShouldDownloadIconThenUseCache() +{ + const QUrl url(s_hostUrl); + + KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); + QVERIFY(willDownload(job)); + QVERIFY(job->exec()); + const QString iconFile = job->iconFile(); + QVERIFY(iconFile.endsWith(QLatin1String("favicons/www.google.com.png"))); + QVERIFY2(QFile::exists(iconFile), qPrintable(iconFile)); + QVERIFY(!QIcon(iconFile).isNull()); // pass full path to QIcon + // This requires https://codereview.qt-project.org/148444 + //QVERIFY(!QIcon::fromTheme(iconFile).isNull()); // old code ported from kdelibs4 might do that, should work too + + // Lookup should give the same result + QCOMPARE(KIO::favIconForUrl(url), iconFile); + + // Second job should use the cache + KIO::FavIconRequestJob *secondJob = new KIO::FavIconRequestJob(url); + QVERIFY(!willDownload(secondJob)); + QVERIFY(secondJob->exec()); + QCOMPARE(secondJob->iconFile(), iconFile); + + // The code from the class docu + QString goticonFile; + { + KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); + connect(job, &KIO::FavIconRequestJob::result, this, [job, &goticonFile](KJob *){ + if (!job->error()) { + goticonFile = job->iconFile(); + } + }); + QVERIFY(job->exec()); + } + QCOMPARE(goticonFile, iconFile); +} + +void FavIconTest::iconUrlJobShouldDownloadIconThenUseCache() +{ + const QUrl url(s_pageUrl); + + // Set icon URL to "ibm" + KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); + job->setIconUrl(QUrl(s_altIconUrl)); + QVERIFY(willDownload(job)); + QVERIFY(job->exec()); + const QString iconFile = job->iconFile(); + QVERIFY(iconFile.endsWith(QLatin1String("favicons/www.ibm.com.png"))); + QVERIFY2(QFile::exists(iconFile), qPrintable(iconFile)); + QVERIFY(!QPixmap(iconFile).isNull()); // pass full path to QPixmap (to test this too) + + // Lookup should give the same result + QCOMPARE(KIO::favIconForUrl(url), iconFile); + + // Second job should use the cache. It doesn't even need the icon url again. + KIO::FavIconRequestJob *secondJob = new KIO::FavIconRequestJob(url); + QVERIFY(!willDownload(secondJob)); + QVERIFY(secondJob->exec()); + QCOMPARE(secondJob->iconFile(), iconFile); + + // Set icon URL to "www.google.fr/favicon.ico" + KIO::FavIconRequestJob *thirdJob = new KIO::FavIconRequestJob(url); + thirdJob->setIconUrl(QUrl(s_thirdIconUrl)); + QVERIFY(willDownload(thirdJob)); + QVERIFY(thirdJob->exec()); + const QString newiconFile = thirdJob->iconFile(); + QVERIFY(newiconFile.endsWith(QLatin1String("favicons/www.google.fr.png"))); + + // Lookup should give the same result + QCOMPARE(KIO::favIconForUrl(url), newiconFile); +} + +void FavIconTest::reloadShouldReload() +{ + const QUrl url(s_hostUrl); + + // First job, to make sure it's in the cache (if the other tests didn't run first) + KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); + QVERIFY(job->exec()); + const QString iconFile = job->iconFile(); + + // Second job should use the cache + KIO::FavIconRequestJob *secondJob = new KIO::FavIconRequestJob(url); + QVERIFY(!willDownload(secondJob)); + QVERIFY(secondJob->exec()); + QCOMPARE(secondJob->iconFile(), iconFile); + + // job with Reload should not use the cache + KIO::FavIconRequestJob *jobWithReload = new KIO::FavIconRequestJob(url, KIO::Reload); + QVERIFY(willDownload(jobWithReload)); + QVERIFY(jobWithReload->exec()); + QCOMPARE(jobWithReload->iconFile(), iconFile); +} + +void FavIconTest::failedDownloadShouldBeRemembered() +{ + const QUrl url(s_pageUrl); + + // Set icon URL to a non-existing favicon + KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); + job->setIconUrl(QUrl("http://www.kde.org/favicon.ico")); + QVERIFY(willDownload(job)); + QVERIFY(!job->exec()); + QVERIFY(job->iconFile().isEmpty()); + QCOMPARE(job->error(), int(KIO::ERR_DOES_NOT_EXIST)); + QCOMPARE(job->errorString(), QStringLiteral("The file or folder http://www.kde.org/favicon.ico does not exist.")); + + // Second job should use the cache and not do anything + KIO::FavIconRequestJob *secondJob = new KIO::FavIconRequestJob(url); + QVERIFY(!willDownload(secondJob)); + QVERIFY(!secondJob->exec()); + QVERIFY(secondJob->iconFile().isEmpty()); + QCOMPARE(job->error(), int(KIO::ERR_DOES_NOT_EXIST)); + QCOMPARE(job->errorString(), QStringLiteral("The file or folder http://www.kde.org/favicon.ico does not exist.")); +} + +void FavIconTest::tooBigFaviconShouldAbort() +{ + const QUrl url(s_pageUrl); + + // Set icon URL to a >65KB file + KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); + job->setIconUrl(QUrl("http://download.kde.org/Attic/4.13.2/src/kcalc-4.13.2.tar.xz")); + QVERIFY(willDownload(job)); + QVERIFY(!job->exec()); + QCOMPARE(job->error(), int(KIO::ERR_SLAVE_DEFINED)); + QCOMPARE(job->errorString(), QStringLiteral("Icon file too big, download aborted")); +} + +void FavIconTest::simultaneousRequestsShouldWork() +{ + const QUrl url(s_hostUrl); + + // First job, to find out the iconFile and delete it + QString iconFile; + { + KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); + QVERIFY(job->exec()); + iconFile = job->iconFile(); + QFile::remove(iconFile); + } + + // This is a case we could maybe optimize: not downloading twice in parallel + KIO::FavIconRequestJob *job1 = new KIO::FavIconRequestJob(url); + job1->setAutoDelete(false); + KIO::FavIconRequestJob *job2 = new KIO::FavIconRequestJob(url); + job2->setAutoDelete(false); + QVERIFY(willDownload(job1)); + QVERIFY(willDownload(job2)); + + QVERIFY(job1->exec()); + QCOMPARE(job1->iconFile(), iconFile); + + QVERIFY(job2->exec()); + QCOMPARE(job2->iconFile(), iconFile); + + delete job1; + delete job2; +} + +static QString getAltIconUrl() +{ + const QUrl url(s_pageUrl); + // Set icon URL to one that we haven't downloaded yet + KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); + job->setIconUrl(QUrl(s_iconUrlForThreadTest)); + job->exec(); + return job->iconFile(); +} + +void FavIconTest::concurrentRequestsShouldWork() +{ + const int numThreads = 3; + QThreadPool tp; + tp.setMaxThreadCount(numThreads); + QVector> futures(numThreads); + for (int i = 0; i < numThreads; ++i) { + futures[i] = QtConcurrent::run(&tp, getAltIconUrl); + } + QVERIFY(tp.waitForDone(60000)); + + const QString firstResult = futures.at(0).result(); + for (int i = 1; i < numThreads; ++i) { + QCOMPARE(futures.at(i).result(), firstResult); + } + QVERIFY(!QPixmap(firstResult).isNull()); +} + +QTEST_MAIN(FavIconTest) + +#include "favicontest.moc" diff --git a/autotests/fileundomanagertest.cpp b/autotests/fileundomanagertest.cpp new file mode 100644 index 0000000..84c4716 --- /dev/null +++ b/autotests/fileundomanagertest.cpp @@ -0,0 +1,688 @@ +/* This file is part of KDE + Copyright (c) 2006, 2008 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include "fileundomanagertest.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#ifdef Q_OS_WIN +#include +#else +#include +#include +#endif + +#include +#include +#include + +QTEST_MAIN(FileUndoManagerTest) + +using namespace KIO; + +static QString homeTmpDir() +{ + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QDir::separator(); +} +static QString destDir() +{ + return homeTmpDir() + "destdir/"; +} + +static QString srcFile() +{ + return homeTmpDir() + "testfile"; +} +static QString destFile() +{ + return destDir() + "testfile"; +} + +#ifndef Q_OS_WIN +static QString srcLink() +{ + return homeTmpDir() + "symlink"; +} +static QString destLink() +{ + return destDir() + "symlink"; +} +#endif + +static QString srcSubDir() +{ + return homeTmpDir() + "subdir"; +} +static QString destSubDir() +{ + return destDir() + "subdir"; +} + +static QList sourceList() +{ + QList lst; + lst << QUrl::fromLocalFile(srcFile()); +#ifndef Q_OS_WIN + lst << QUrl::fromLocalFile(srcLink()); +#endif + return lst; +} + +static void createTestFile(const QString &path, const char *contents) +{ + QFile f(path); + if (!f.open(QIODevice::WriteOnly)) { + qFatal("Couldn't create %s", qPrintable(path)); + } + f.write(QByteArray(contents)); + f.close(); +} + +static void createTestSymlink(const QString &path) +{ + // Create symlink if it doesn't exist yet + QT_STATBUF buf; + if (QT_LSTAT(QFile::encodeName(path).constData(), &buf) != 0) { + bool ok = KIOPrivate::createSymlink(QStringLiteral("/IDontExist"), path); // broken symlink + if (!ok) { + qFatal("couldn't create symlink: %s", strerror(errno)); + } + QVERIFY(QT_LSTAT(QFile::encodeName(path).constData(), &buf) == 0); + QVERIFY((buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK); + } else { + QVERIFY((buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK); + } + qDebug("symlink %s created", qPrintable(path)); + QVERIFY(QFileInfo(path).isSymLink()); +} + +static void checkTestDirectory(const QString &path) +{ + QVERIFY(QFileInfo(path).isDir()); + QVERIFY(QFileInfo(path + "/fileindir").isFile()); +#ifndef Q_OS_WIN + QVERIFY(QFileInfo(path + "/testlink").isSymLink()); +#endif + QVERIFY(QFileInfo(path + "/dirindir").isDir()); + QVERIFY(QFileInfo(path + "/dirindir/nested").isFile()); +} + +static void createTestDirectory(const QString &path) +{ + QDir dir; + bool ok = dir.mkpath(path); + if (!ok) { + qFatal("couldn't create %s", qPrintable(path)); + } + createTestFile(path + "/fileindir", "File in dir"); +#ifndef Q_OS_WIN + createTestSymlink(path + "/testlink"); +#endif + ok = dir.mkdir(path + "/dirindir"); + if (!ok) { + qFatal("couldn't create %s", qPrintable(path)); + } + createTestFile(path + "/dirindir/nested", "Nested"); + checkTestDirectory(path); +} + +class TestUiInterface : public FileUndoManager::UiInterface +{ +public: + TestUiInterface() : FileUndoManager::UiInterface(), m_nextReplyToConfirmDeletion(true) + { + setShowProgressInfo(false); + } + void jobError(KIO::Job *job) Q_DECL_OVERRIDE { + qFatal("%s", qPrintable(job->errorString())); + } + bool copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime) Q_DECL_OVERRIDE { + Q_UNUSED(src); + m_dest = dest; + Q_UNUSED(srcTime); + Q_UNUSED(destTime); + return true; + } + bool confirmDeletion(const QList &files) Q_DECL_OVERRIDE { + m_files = files; + return m_nextReplyToConfirmDeletion; + } + void setNextReplyToConfirmDeletion(bool b) + { + m_nextReplyToConfirmDeletion = b; + } + QList files() const + { + return m_files; + } + QUrl dest() const + { + return m_dest; + } + void clear() + { + m_dest = QUrl(); + m_files.clear(); + } +private: + bool m_nextReplyToConfirmDeletion; + QUrl m_dest; + QList m_files; +}; + +void FileUndoManagerTest::initTestCase() +{ + qDebug("initTestCase"); + + QStandardPaths::enableTestMode(true); + + // Get kio_trash to share our environment so that it writes trashrc to the right kdehome + qputenv("KDE_FORK_SLAVES", "yes"); + qputenv("KIOSLAVE_ENABLE_TESTMODE", "1"); + + // Start with a clean base dir + cleanupTestCase(); + + if (!QFile::exists(homeTmpDir())) { + bool ok = QDir().mkpath(homeTmpDir()); + if (!ok) { + qFatal("Couldn't create %s", qPrintable(homeTmpDir())); + } + } + + createTestFile(srcFile(), "Hello world"); +#ifndef Q_OS_WIN + createTestSymlink(srcLink()); +#endif + createTestDirectory(srcSubDir()); + + QDir().mkpath(destDir()); + QVERIFY(QFileInfo(destDir()).isDir()); + + QVERIFY(!FileUndoManager::self()->undoAvailable()); + m_uiInterface = new TestUiInterface; // owned by FileUndoManager + FileUndoManager::self()->setUiInterface(m_uiInterface); +} + +void FileUndoManagerTest::cleanupTestCase() +{ + KIO::Job *job = KIO::del(QUrl::fromLocalFile(homeTmpDir()), KIO::HideProgressInfo); + job->exec(); +} + +void FileUndoManagerTest::doUndo() +{ + QEventLoop eventLoop; + bool ok = connect(FileUndoManager::self(), SIGNAL(undoJobFinished()), + &eventLoop, SLOT(quit())); + QVERIFY(ok); + + FileUndoManager::self()->undo(); + eventLoop.exec(QEventLoop::ExcludeUserInputEvents); // wait for undo job to finish +} + +void FileUndoManagerTest::testCopyFiles() +{ + qDebug(); + // Initially inspired from JobTest::copyFileToSamePartition() + const QString destdir = destDir(); + QList lst = sourceList(); + const QUrl d = QUrl::fromLocalFile(destdir); + KIO::CopyJob *job = KIO::copy(lst, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + FileUndoManager::self()->recordCopyJob(job); + + QSignalSpy spyUndoAvailable(FileUndoManager::self(), SIGNAL(undoAvailable(bool))); + QVERIFY(spyUndoAvailable.isValid()); + QSignalSpy spyTextChanged(FileUndoManager::self(), SIGNAL(undoTextChanged(QString))); + QVERIFY(spyTextChanged.isValid()); + + bool ok = job->exec(); + QVERIFY(ok); + + QVERIFY(QFile::exists(destFile())); +#ifndef Q_OS_WIN + // Don't use QFile::exists, it's a broken symlink... + QVERIFY(QFileInfo(destLink()).isSymLink()); +#endif + + // might have to wait for dbus signal here... but this is currently disabled. + //QTest::qWait( 20 ); + QVERIFY(FileUndoManager::self()->undoAvailable()); + QCOMPARE(spyUndoAvailable.count(), 1); + QCOMPARE(spyTextChanged.count(), 1); + m_uiInterface->clear(); + + m_uiInterface->setNextReplyToConfirmDeletion(false); // act like the user didn't confirm + FileUndoManager::self()->undo(); + QCOMPARE(m_uiInterface->files().count(), 1); // confirmDeletion was called + QCOMPARE(m_uiInterface->files()[0].toString(), QUrl::fromLocalFile(destFile()).toString()); + QVERIFY(QFile::exists(destFile())); // nothing happened yet + + // OK, now do it + m_uiInterface->clear(); + m_uiInterface->setNextReplyToConfirmDeletion(true); + doUndo(); + + QVERIFY(!FileUndoManager::self()->undoAvailable()); + QVERIFY(spyUndoAvailable.count() >= 2); // it's in fact 3, due to lock/unlock emitting it as well + QCOMPARE(spyTextChanged.count(), 2); + QCOMPARE(m_uiInterface->files().count(), 1); // confirmDeletion was called + QCOMPARE(m_uiInterface->files()[0].toString(), QUrl::fromLocalFile(destFile()).toString()); + + // Check that undo worked + QVERIFY(!QFile::exists(destFile())); +#ifndef Q_OS_WIN + QVERIFY(!QFile::exists(destLink())); + QVERIFY(!QFileInfo(destLink()).isSymLink()); +#endif +} + +void FileUndoManagerTest::testMoveFiles() +{ + qDebug(); + const QString destdir = destDir(); + QList lst = sourceList(); + const QUrl d = QUrl::fromLocalFile(destdir); + KIO::CopyJob *job = KIO::move(lst, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + FileUndoManager::self()->recordCopyJob(job); + + bool ok = job->exec(); + QVERIFY(ok); + + QVERIFY(!QFile::exists(srcFile())); // the source moved + QVERIFY(QFile::exists(destFile())); +#ifndef Q_OS_WIN + QVERIFY(!QFileInfo(srcLink()).isSymLink()); + // Don't use QFile::exists, it's a broken symlink... + QVERIFY(QFileInfo(destLink()).isSymLink()); +#endif + + doUndo(); + + QVERIFY(QFile::exists(srcFile())); // the source is back + QVERIFY(!QFile::exists(destFile())); +#ifndef Q_OS_WIN + QVERIFY(QFileInfo(srcLink()).isSymLink()); + QVERIFY(!QFileInfo(destLink()).isSymLink()); +#endif +} + +// Testing for overwrite isn't possible, because non-interactive jobs never overwrite. +// And nothing different happens anyway, the dest is removed... +#if 0 +void FileUndoManagerTest::testCopyFilesOverwrite() +{ + qDebug(); + // Create a different file in the destdir + createTestFile(destFile(), "An old file already in the destdir"); + + testCopyFiles(); +} +#endif + +void FileUndoManagerTest::testCopyDirectory() +{ + const QString destdir = destDir(); + QList lst; lst << QUrl::fromLocalFile(srcSubDir()); + const QUrl d = QUrl::fromLocalFile(destdir); + KIO::CopyJob *job = KIO::copy(lst, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + FileUndoManager::self()->recordCopyJob(job); + + bool ok = job->exec(); + QVERIFY(ok); + + checkTestDirectory(srcSubDir()); // src untouched + checkTestDirectory(destSubDir()); + + doUndo(); + + checkTestDirectory(srcSubDir()); + QVERIFY(!QFile::exists(destSubDir())); +} + +void FileUndoManagerTest::testMoveDirectory() +{ + const QString destdir = destDir(); + QList lst; lst << QUrl::fromLocalFile(srcSubDir()); + const QUrl d = QUrl::fromLocalFile(destdir); + KIO::CopyJob *job = KIO::move(lst, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + FileUndoManager::self()->recordCopyJob(job); + + bool ok = job->exec(); + QVERIFY(ok); + + QVERIFY(!QFile::exists(srcSubDir())); + checkTestDirectory(destSubDir()); + + doUndo(); + + checkTestDirectory(srcSubDir()); + QVERIFY(!QFile::exists(destSubDir())); +} + +void FileUndoManagerTest::testRenameFile() +{ + const QUrl oldUrl = QUrl::fromLocalFile(srcFile()); + const QUrl newUrl = QUrl::fromLocalFile(srcFile() + ".new"); + QList lst; + lst.append(oldUrl); + QSignalSpy spyUndoAvailable(FileUndoManager::self(), SIGNAL(undoAvailable(bool))); + QVERIFY(spyUndoAvailable.isValid()); + KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); + job->setUiDelegate(0); + FileUndoManager::self()->recordJob(FileUndoManager::Rename, lst, newUrl, job); + + bool ok = job->exec(); + QVERIFY(ok); + + QVERIFY(!QFile::exists(srcFile())); + QVERIFY(QFileInfo(newUrl.toLocalFile()).isFile()); + QCOMPARE(spyUndoAvailable.count(), 1); + + doUndo(); + + QVERIFY(QFile::exists(srcFile())); + QVERIFY(!QFileInfo(newUrl.toLocalFile()).isFile()); +} + +void FileUndoManagerTest::testRenameDir() +{ + const QUrl oldUrl = QUrl::fromLocalFile(srcSubDir()); + const QUrl newUrl = QUrl::fromLocalFile(srcSubDir() + ".new"); + QList lst; + lst.append(oldUrl); + KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); + job->setUiDelegate(0); + FileUndoManager::self()->recordJob(FileUndoManager::Rename, lst, newUrl, job); + + bool ok = job->exec(); + QVERIFY(ok); + + QVERIFY(!QFile::exists(srcSubDir())); + QVERIFY(QFileInfo(newUrl.toLocalFile()).isDir()); + + doUndo(); + + QVERIFY(QFile::exists(srcSubDir())); + QVERIFY(!QFileInfo(newUrl.toLocalFile()).isDir()); +} + +void FileUndoManagerTest::testCreateSymlink() +{ +#ifdef Q_OS_WIN + QSKIP("Test skipped on Windows for lack of proper symlink support"); +#endif + const QUrl link = QUrl::fromLocalFile(homeTmpDir() + "newlink"); + const QString path = link.toLocalFile(); + QVERIFY(!QFile::exists(path)); + + const QUrl target = QUrl::fromLocalFile(homeTmpDir() + "linktarget"); + const QString targetPath = target.toLocalFile(); + createTestFile(targetPath, "Link's Target"); + QVERIFY(QFile::exists(targetPath)); + + KIO::CopyJob *job = KIO::link(target, link); + job->setUiDelegate(0); + FileUndoManager::self()->recordCopyJob(job); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QFile::exists(path)); + QVERIFY(QFileInfo(path).isSymLink()); + + // For undoing symlinks no confirmation is required. We delete it straight away. + doUndo(); + + QVERIFY(!QFile::exists(path)); +} + +void FileUndoManagerTest::testCreateDir() +{ + const QUrl url = QUrl::fromLocalFile(srcSubDir() + ".mkdir"); + const QString path = url.toLocalFile(); + QVERIFY(!QFile::exists(path)); + + KIO::SimpleJob *job = KIO::mkdir(url); + job->setUiDelegate(0); + FileUndoManager::self()->recordJob(FileUndoManager::Mkdir, QList(), url, job); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QFile::exists(path)); + QVERIFY(QFileInfo(path).isDir()); + + m_uiInterface->clear(); + m_uiInterface->setNextReplyToConfirmDeletion(false); // act like the user didn't confirm + FileUndoManager::self()->undo(); + QCOMPARE(m_uiInterface->files().count(), 1); // confirmDeletion was called + QCOMPARE(m_uiInterface->files()[0].toString(), url.toString()); + QVERIFY(QFile::exists(path)); // nothing happened yet + + // OK, now do it + m_uiInterface->clear(); + m_uiInterface->setNextReplyToConfirmDeletion(true); + doUndo(); + + QVERIFY(!QFile::exists(path)); +} + +void FileUndoManagerTest::testMkpath() +{ + const QString parent = srcSubDir() + "mkpath"; + const QString path = parent + "/subdir"; + QVERIFY(!QFile::exists(path)); + const QUrl url = QUrl::fromLocalFile(path); + + KIO::Job *job = KIO::mkpath(url); + job->setUiDelegate(0); + FileUndoManager::self()->recordJob(FileUndoManager::Mkpath, QList(), url, job); + QVERIFY(job->exec()); + QVERIFY(QFileInfo(path).isDir()); + + m_uiInterface->clear(); + m_uiInterface->setNextReplyToConfirmDeletion(true); + doUndo(); + + QVERIFY(!FileUndoManager::self()->undoAvailable()); + QCOMPARE(m_uiInterface->files().count(), 2); // confirmDeletion was called + QCOMPARE(m_uiInterface->files()[0].toLocalFile(), path); + QCOMPARE(m_uiInterface->files()[1].toLocalFile(), parent); + + QVERIFY(!QFile::exists(path)); +} + +void FileUndoManagerTest::testTrashFiles() +{ + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("trash"))) { + QSKIP("kio_trash not installed"); + } + + // Trash it all at once: the file, the symlink, the subdir. + QList lst = sourceList(); + lst.append(QUrl::fromLocalFile(srcSubDir())); + KIO::Job *job = KIO::trash(lst, KIO::HideProgressInfo); + job->setUiDelegate(0); + FileUndoManager::self()->recordJob(FileUndoManager::Trash, lst, QUrl(QStringLiteral("trash:/")), job); + + bool ok = job->exec(); + QVERIFY(ok); + + // Check that things got removed + QVERIFY(!QFile::exists(srcFile())); +#ifndef Q_OS_WIN + QVERIFY(!QFileInfo(srcLink()).isSymLink()); +#endif + QVERIFY(!QFile::exists(srcSubDir())); + + // check trash? + // Let's just check that it's not empty. kio_trash has its own unit tests anyway. + KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig); + QVERIFY(cfg.hasGroup("Status")); + QCOMPARE(cfg.group("Status").readEntry("Empty", true), false); + + doUndo(); + + QVERIFY(QFile::exists(srcFile())); +#ifndef Q_OS_WIN + QVERIFY(QFileInfo(srcLink()).isSymLink()); +#endif + QVERIFY(QFile::exists(srcSubDir())); + + // We can't check that the trash is empty; other partitions might have their own trash +} + +void FileUndoManagerTest::testRestoreTrashedFiles() +{ + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("trash"))) { + QSKIP("kio_trash not installed"); + } + + // Trash it all at once: the file, the symlink, the subdir. + const QFile::Permissions origPerms = QFileInfo(srcFile()).permissions(); + QList lst = sourceList(); + lst.append(QUrl::fromLocalFile(srcSubDir())); + KIO::Job *job = KIO::trash(lst, KIO::HideProgressInfo); + job->setUiDelegate(0); + QVERIFY(job->exec()); + + const QMap metaData = job->metaData(); + QList trashUrls; + foreach (const QUrl &src, lst) { + QMap::ConstIterator it = metaData.find("trashURL-" + src.path()); + QVERIFY(it != metaData.constEnd()); + trashUrls.append(QUrl(it.value())); + } + + qDebug() << trashUrls; + + // Restore from trash + KIO::RestoreJob *restoreJob = KIO::restoreFromTrash(trashUrls, KIO::HideProgressInfo); + restoreJob->setUiDelegate(0); + QVERIFY(restoreJob->exec()); + + QVERIFY(QFile::exists(srcFile())); + QCOMPARE(QFileInfo(srcFile()).permissions(), origPerms); +#ifndef Q_OS_WIN + QVERIFY(QFileInfo(srcLink()).isSymLink()); +#endif + QVERIFY(QFile::exists(srcSubDir())); + + // TODO support for RestoreJob in FileUndoManager !!! +} + +static void setTimeStamp(const QString &path) +{ +#ifdef Q_OS_UNIX + // Put timestamp in the past so that we can check that the + // copy actually preserves it. + struct timeval tp; + gettimeofday(&tp, 0); + struct utimbuf utbuf; + utbuf.actime = tp.tv_sec + 30; // 30 seconds in the future + utbuf.modtime = tp.tv_sec + 60; // 60 second in the future + utime(QFile::encodeName(path).constData(), &utbuf); + qDebug("Time changed for %s", qPrintable(path)); +#endif +} + +void FileUndoManagerTest::testModifyFileBeforeUndo() +{ + // based on testCopyDirectory (so that we check that it works for files in subdirs too) + const QString destdir = destDir(); + QList lst; lst << QUrl::fromLocalFile(srcSubDir()); + const QUrl d = QUrl::fromLocalFile(destdir); + KIO::CopyJob *job = KIO::copy(lst, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + FileUndoManager::self()->recordCopyJob(job); + + bool ok = job->exec(); + QVERIFY(ok); + + checkTestDirectory(srcSubDir()); // src untouched + checkTestDirectory(destSubDir()); + const QString destFile = destSubDir() + "/fileindir"; + setTimeStamp(destFile); // simulate a modification of the file + + doUndo(); + + // Check that TestUiInterface::copiedFileWasModified got called + QCOMPARE(m_uiInterface->dest().toLocalFile(), destFile); + + checkTestDirectory(srcSubDir()); + QVERIFY(!QFile::exists(destSubDir())); +} + +void FileUndoManagerTest::testPasteClipboardUndo() +{ + const QList urls(sourceList()); + QMimeData *mimeData = new QMimeData(); + mimeData->setUrls(urls); + KIO::setClipboardDataCut(mimeData, true); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setMimeData(mimeData); + + // Paste the contents of the clipboard and check its status + QUrl destDirUrl = QUrl::fromLocalFile(destDir()); + KIO::Job *job = KIO::paste(mimeData, destDirUrl); + QVERIFY(job); + QVERIFY(job->exec()); + + // Check if the clipboard was updated after paste operation + QList urls2; + Q_FOREACH (const QUrl &url, urls) { + QUrl dUrl = destDirUrl.adjusted(QUrl::StripTrailingSlash); + dUrl.setPath(dUrl.path() + '/' + url.fileName()); + urls2 << dUrl; + } + QList clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData()); + QCOMPARE(clipboardUrls, urls2); + + // Check if the clipboard was updated after undo operation + doUndo(); + clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData()); + QCOMPARE(clipboardUrls, urls); +} + +// TODO: add test (and fix bug) for DND of remote urls / "Link here" (creates .desktop files) // Undo (doesn't do anything) +// TODO: add test for interrupting a moving operation and then using Undo - bug:91579 diff --git a/autotests/fileundomanagertest.h b/autotests/fileundomanagertest.h new file mode 100644 index 0000000..cd2394b --- /dev/null +++ b/autotests/fileundomanagertest.h @@ -0,0 +1,60 @@ +/* This file is part of KDE + Copyright (c) 2006, 2008 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef FILEUNDOMANAGERTEST_H +#define FILEUNDOMANAGERTEST_H + +#include +#include +class TestUiInterface; + +class FileUndoManagerTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testCopyFiles(); + void testMoveFiles(); + void testCopyDirectory(); + void testMoveDirectory(); + void testRenameFile(); + void testRenameDir(); + void testTrashFiles(); + void testRestoreTrashedFiles(); + void testModifyFileBeforeUndo(); // #20532 + void testCreateSymlink(); + void testCreateDir(); + void testMkpath(); + void testPasteClipboardUndo(); // #318757 + + // TODO find tests that would lead to kio job errors + + // TODO test renaming during a CopyJob. + // Doesn't seem possible though, requires user interaction... + + // TODO: add test for undoing after a partial move (http://bugs.kde.org/show_bug.cgi?id=91579) + // Difficult too. + +private: + void doUndo(); + TestUiInterface *m_uiInterface; +}; + +#endif diff --git a/autotests/globaltest.cpp b/autotests/globaltest.cpp new file mode 100644 index 0000000..d2484ae --- /dev/null +++ b/autotests/globaltest.cpp @@ -0,0 +1,116 @@ +/* This file is part of the KDE project + Copyright (C) 2013 Dawit Alemayehu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "globaltest.h" +#include + +#include "global.h" +#include "kioglobal_p.h" + +#include +#include + +#include + +QTEST_MAIN(GlobalTest) + +void GlobalTest::testUserPermissionConversion() +{ + const int permissions = S_IRUSR | S_IWUSR | S_IXUSR; + QFile::Permissions qPermissions = KIO::convertPermissions(permissions); + + QFile::Permissions perms = (QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + QCOMPARE(qPermissions & perms, perms); + + perms = (QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup); + QCOMPARE(qPermissions & perms, 0); + + perms = (QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); + QCOMPARE(qPermissions & perms, 0); +} + +void GlobalTest::testGroupPermissionConversion() +{ + const int permissions = S_IRGRP | S_IWGRP | S_IXGRP; + QFile::Permissions qPermissions = KIO::convertPermissions(permissions); + + QFile::Permissions perms = (QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + QCOMPARE(qPermissions & perms, 0); + + perms = (QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup); + QCOMPARE(qPermissions & perms, perms); + + perms = (QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); + QCOMPARE(qPermissions & perms, 0); +} + +void GlobalTest::testOtherPermissionConversion() +{ + const int permissions = S_IROTH | S_IWOTH | S_IXOTH; + QFile::Permissions qPermissions = KIO::convertPermissions(permissions); + + QFile::Permissions perms = (QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + QCOMPARE(qPermissions & perms, 0); + + perms = (QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup); + QCOMPARE(qPermissions & perms, 0); + + perms = (QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); + QCOMPARE(qPermissions & perms, perms); +} + +void GlobalTest::testSuggestName_data() +{ + QTest::addColumn("oldName"); + QTest::addColumn("existingFiles"); + QTest::addColumn("expectedOutput"); + + QTest::newRow("non-existing") << "foobar" << QStringList() << "foobar (1)"; + QTest::newRow("existing") << "foobar" << QStringList(QStringLiteral("foobar")) << "foobar (1)"; + QTest::newRow("existing_1") << "foobar" << (QStringList() << QStringLiteral("foobar") << QStringLiteral("foobar (1)")) << "foobar (2)"; + QTest::newRow("extension") << "foobar.txt" << QStringList() << "foobar (1).txt"; + QTest::newRow("extension_exists") << "foobar.txt" << (QStringList() << QStringLiteral("foobar.txt")) << "foobar (1).txt"; + QTest::newRow("extension_exists_1") << "foobar.txt" << (QStringList() << QStringLiteral("foobar.txt") << QStringLiteral("foobar (1).txt")) << "foobar (2).txt"; + QTest::newRow("two_extensions") << "foobar.tar.gz" << QStringList() << "foobar (1).tar.gz"; + QTest::newRow("two_extensions_exists") << "foobar.tar.gz" << (QStringList() << QStringLiteral("foobar.tar.gz")) << "foobar (1).tar.gz"; + QTest::newRow("two_extensions_exists_1") << "foobar.tar.gz" << (QStringList() << QStringLiteral("foobar.tar.gz") << QStringLiteral("foobar (1).tar.gz")) << "foobar (2).tar.gz"; + QTest::newRow("with_space") << "foo bar" << QStringList(QStringLiteral("foo bar")) << "foo bar (1)"; + QTest::newRow("dot_at_beginning") << ".aFile.tar.gz" << QStringList() << ".aFile (1).tar.gz"; + QTest::newRow("dots_at_beginning") << "..aFile.tar.gz" << QStringList() << "..aFile (1).tar.gz"; + QTest::newRow("empty_basename") << ".txt" << QStringList() << ". (1).txt"; + QTest::newRow("empty_basename_2dots") << "..txt" << QStringList() << ". (1).txt"; + QTest::newRow("basename_with_dots") << "filename.5.3.2.tar.gz" << QStringList() << "filename.5.3.2 (1).tar.gz"; + QTest::newRow("unknown_extension_trashinfo") << "fileFromHome.trashinfo" << QStringList() << "fileFromHome (1).trashinfo"; +} + +void GlobalTest::testSuggestName() +{ + QFETCH(QString, oldName); + QFETCH(QStringList, existingFiles); + QFETCH(QString, expectedOutput); + + QTemporaryDir dir; + const QUrl baseUrl = QUrl::fromLocalFile(dir.path()); + foreach (const QString &localFile, existingFiles) { + QFile file(dir.path() + '/' + localFile); + QVERIFY(file.open(QIODevice::WriteOnly)); + } + QCOMPARE(KIO::suggestName(baseUrl, oldName), expectedOutput); +} + diff --git a/autotests/globaltest.h b/autotests/globaltest.h new file mode 100644 index 0000000..246fb24 --- /dev/null +++ b/autotests/globaltest.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright (C) 2013 Dawit Alemayehu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KIO_GLOBALTEST_H +#define KIO_GLOBALTEST_H + +#include + +class GlobalTest : public QObject +{ + Q_OBJECT + +public: + GlobalTest() {} + +private Q_SLOTS: + void testUserPermissionConversion(); + void testGroupPermissionConversion(); + void testOtherPermissionConversion(); + void testSuggestName_data(); + void testSuggestName(); +}; + +#endif diff --git a/autotests/http/CMakeLists.txt b/autotests/http/CMakeLists.txt new file mode 100644 index 0000000..190db42 --- /dev/null +++ b/autotests/http/CMakeLists.txt @@ -0,0 +1,68 @@ +find_package(Qt5Test REQUIRED) +find_package(Qt5Widgets REQUIRED) +find_package(KF5Archive ${KF5_DEP_VERSION} REQUIRED) +find_package(ZLIB) +set_package_properties(ZLIB PROPERTIES DESCRIPTION "Support for gzip compressed files and data streams" + URL "http://www.zlib.net" + TYPE REQUIRED + PURPOSE "Required for httpfiltertest" + ) + +include_directories("${CMAKE_CURRENT_BINARY_DIR}/../../src/ioslaves/http" "${CMAKE_CURRENT_SOURCE_DIR}/../../src/ioslaves/http" ) + +include(ECMAddTests) + +ecm_add_test(httpheadertokenizetest.cpp + NAME_PREFIX "kioslave-" + LINK_LIBRARIES KF5::I18n Qt5::Test Qt5::Widgets +) + +ecm_add_test(httpheaderdispositiontest.cpp + NAME_PREFIX "kioslave-" + LINK_LIBRARIES KF5::I18n Qt5::Test Qt5::Widgets +) + +ecm_add_test(httpauthenticationtest.cpp + NAME_PREFIX "kioslave-" + LINK_LIBRARIES + Qt5::Test + Qt5::Network + Qt5::Widgets + KF5::I18n + KF5::KIOCore + KF5::KIONTLM +) + +if(GSSAPI_FOUND) + target_link_libraries(httpauthenticationtest ${GSSAPI_LIBS}) +endif() + +set(httpobjecttest_SRCS + httpobjecttest.cpp + ${kioslave-http_SOURCE_DIR}/http.cpp + ${kioslave-http_SOURCE_DIR}/httpauthentication.cpp + ${kioslave-http_SOURCE_DIR}/httpfilter.cpp +) + +ecm_add_test(${httpobjecttest_SRCS} + TEST_NAME "httpobjecttest" NAME_PREFIX "kioslave-" + LINK_LIBRARIES + Qt5::Test + Qt5::DBus + Qt5::Widgets + Qt5::Network # QLocalSocket + Qt5::Xml # QDomElement + KF5::I18n + KF5::KIOCore + KF5::KIONTLM + KF5::Archive +) +if(GSSAPI_FOUND) + target_link_libraries(httpobjecttest ${GSSAPI_LIBS}) +endif() + +ecm_add_test(httpfiltertest.cpp ${kioslave-http_SOURCE_DIR}/httpfilter.cpp + TEST_NAME httpfiltertest + LINK_LIBRARIES Qt5::Test KF5::I18n KF5::Archive ${ZLIB_LIBRARY}) +target_include_directories(httpfiltertest PRIVATE ${ZLIB_INCLUDE_DIRS}) + diff --git a/autotests/http/httpauthenticationtest.cpp b/autotests/http/httpauthenticationtest.cpp new file mode 100644 index 0000000..c0d3ddc --- /dev/null +++ b/autotests/http/httpauthenticationtest.cpp @@ -0,0 +1,344 @@ +/* This file is part of the KDE libraries + Copyright (C) 2011 Dawit Alemayehu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "httpauthenticationtest.h" + +#include + +#include +#include +#include +#include +#include + +#define ENABLE_HTTP_AUTH_NONCE_SETTER +#include "httpauthentication.cpp" + +// QT5 TODO QTEST_GUILESS_MAIN(HTTPAuthenticationTest) +QTEST_MAIN(HTTPAuthenticationTest) + +static void parseAuthHeader(const QByteArray &header, + QByteArray *bestOffer, + QByteArray *scheme, + QList *result) +{ + const QList authHeaders = KAbstractHttpAuthentication::splitOffers(QList() << header); + QByteArray chosenHeader = KAbstractHttpAuthentication::bestOffer(authHeaders); + + if (bestOffer) { + *bestOffer = chosenHeader; + } + + if (!scheme && !result) { + return; + } + + QByteArray authScheme; + const QList parseResult = parseChallenge(chosenHeader, &authScheme); + + if (scheme) { + *scheme = authScheme; + } + + if (result) { + *result = parseResult; + } +} + +static QByteArray hmacMD5(const QByteArray &data, const QByteArray &key) +{ + QByteArray ret; + QByteArray ipad(64, 0x36); + QByteArray opad(64, 0x5c); + + Q_ASSERT(key.size() <= 64); + + for (int i = qMin(key.size(), 64) - 1; i >= 0; i--) { + ipad.data()[i] ^= key[i]; + opad.data()[i] ^= key[i]; + } + + QByteArray content(ipad + data); + + QCryptographicHash md5(QCryptographicHash::Md5); + md5.addData(content); + content = opad + md5.result(); + + md5.reset(); + md5.addData(content); + + return md5.result(); +} + +static QByteArray QString2UnicodeLE(const QString &target) +{ + QByteArray unicode(target.length() * 2, 0); + + for (int i = 0; i < target.length(); i++) { + ((quint16 *) unicode.data()) [ i ] = qToLittleEndian(target[i].unicode()); + } + + return unicode; +} + +void HTTPAuthenticationTest::testHeaderParsing_data() +{ + QTest::addColumn("header"); + QTest::addColumn("resultScheme"); + QTest::addColumn("resultValues"); + + // Tests cases from http://greenbytes.de/tech/tc/httpauth/ + QTest::newRow("greenbytes-simplebasic") << QByteArray("Basic realm=\"foo\"") << QByteArray("Basic") << QByteArray("realm,foo"); + QTest::newRow("greenbytes-simplebasictok") << QByteArray("Basic realm=foo") << QByteArray("Basic") << QByteArray("realm,foo"); + QTest::newRow("greenbytes-simplebasiccomma") << QByteArray("Basic , realm=\"foo\"") << QByteArray("Basic") << QByteArray("realm,foo"); + // there must be a space after the scheme + QTest::newRow("greenbytes-simplebasiccomma2") << QByteArray("Basic, realm=\"foo\"") << QByteArray() << QByteArray(); + // we accept scheme without any parameters to maintain compatibility with too simple minded servers out there + QTest::newRow("greenbytes-simplebasicnorealm") << QByteArray("Basic") << QByteArray("Basic") << QByteArray(); + QTest::newRow("greenbytes-simplebasicwsrealm") << QByteArray("Basic realm = \"foo\"") << QByteArray("Basic") << QByteArray("realm,foo"); + QTest::newRow("greenbytes-simplebasicrealmsqc") << QByteArray("Basic realm=\"\\f\\o\\o\"") << QByteArray("Basic") << QByteArray("realm,foo"); + QTest::newRow("greenbytes-simplebasicrealmsqc2") << QByteArray("Basic realm=\"\\\"foo\\\"\"") << QByteArray("Basic") << QByteArray("realm,\"foo\""); + QTest::newRow("greenbytes-simplebasicnewparam1") << QByteArray("Basic realm=\"foo\", bar=\"xyz\"") << QByteArray("Basic") << QByteArray("realm,foo,bar,xyz"); + QTest::newRow("greenbytes-simplebasicnewparam2") << QByteArray("Basic bar=\"xyz\", realm=\"foo\"") << QByteArray("Basic") << QByteArray("bar,xyz,realm,foo"); + // a Basic challenge following an empty one + QTest::newRow("greenbytes-multibasicempty") << QByteArray(",Basic realm=\"foo\"") << QByteArray("Basic") << QByteArray("realm,foo"); + QTest::newRow("greenbytes-multibasicunknown") << QByteArray("Basic realm=\"basic\", Newauth realm=\"newauth\"") << QByteArray("Basic") << QByteArray("realm,basic"); + QTest::newRow("greenbytes-multibasicunknown2") << QByteArray("Newauth realm=\"newauth\", Basic realm=\"basic\"") << QByteArray("Basic") << QByteArray("realm,basic"); + QTest::newRow("greenbytes-unknown") << QByteArray("Newauth realm=\"newauth\"") << QByteArray() << QByteArray(); + + // Misc. test cases + QTest::newRow("ntlm") << QByteArray("NTLM ") << QByteArray("NTLM") << QByteArray(); + QTest::newRow("unterminated-quoted-value") << QByteArray("Basic realm=\"") << QByteArray("Basic") << QByteArray(); + QTest::newRow("spacing-and-tabs") << QByteArray("bAsic bar\t =\t\"baz\", realm =\t\"foo\"") << QByteArray("bAsic") << QByteArray("bar,baz,realm,foo"); + QTest::newRow("empty-fields") << QByteArray("Basic realm=foo , , , ,, bar=\"baz\"\t,") << QByteArray("Basic") << QByteArray("realm,foo,bar,baz"); + QTest::newRow("spacing") << QByteArray("Basic realm=foo, bar = baz") << QByteArray("Basic") << QByteArray("realm,foo,bar,baz"); + QTest::newRow("missing-comma-between-fields") << QByteArray("Basic realm=foo bar = baz") << QByteArray("Basic") << QByteArray("realm,foo"); + // quotes around text, every character needlessly quoted + QTest::newRow("quote-excess") << QByteArray("Basic realm=\"\\\"\\f\\o\\o\\\"\"") << QByteArray("Basic") << QByteArray("realm,\"foo\""); + // quotes around text, quoted backslashes + QTest::newRow("quoted-backslash") << QByteArray("Basic realm=\"\\\"foo\\\\\\\\\"") << QByteArray("Basic") << QByteArray("realm,\"foo\\\\"); + // quotes around text, quoted backslashes, quote hidden behind them + QTest::newRow("quoted-backslash-and-quote") << QByteArray("Basic realm=\"\\\"foo\\\\\\\"\"") << QByteArray("Basic") << QByteArray("realm,\"foo\\\""); + // invalid quoted text + QTest::newRow("invalid-quoted") << QByteArray("Basic realm=\"\\\"foo\\\\\\\"") << QByteArray("Basic") << QByteArray(); + // ends in backslash without quoted value + QTest::newRow("invalid-quote") << QByteArray("Basic realm=\"\\\"foo\\\\\\") << QByteArray("Basic") << QByteArray(); +} + +QByteArray joinQByteArray(const QList &list) +{ + QByteArray data; + const int count = list.count(); + + for (int i = 0; i < count; ++i) { + if (i > 0) { + data += ','; + } + data += list.at(i); + } + + return data; +} + +void HTTPAuthenticationTest::testHeaderParsing() +{ + QFETCH(QByteArray, header); + QFETCH(QByteArray, resultScheme); + QFETCH(QByteArray, resultValues); + + QByteArray chosenHeader, chosenScheme; + QList parsingResult, expectedResult; + parseAuthHeader(header, &chosenHeader, &chosenScheme, &parsingResult); + QCOMPARE(chosenScheme, resultScheme); + QCOMPARE(joinQByteArray(parsingResult), resultValues); +} + +void HTTPAuthenticationTest::testAuthenticationSelection_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expectedScheme"); + QTest::addColumn("expectedOffer"); + +#if HAVE_LIBGSSAPI + QTest::newRow("all-with-negotiate") << QByteArray("Negotiate , Digest , NTLM , Basic") << QByteArray("Negotiate") << QByteArray("Negotiate"); +#endif + QTest::newRow("all-without-negotiate") << QByteArray("Digest , NTLM , Basic , NewAuth") << QByteArray("Digest") << QByteArray("Digest"); + QTest::newRow("ntlm-basic-unknown") << QByteArray("NTLM , Basic , NewAuth") << QByteArray("NTLM") << QByteArray("NTLM"); + QTest::newRow("basic-unknown") << QByteArray("Basic , NewAuth") << QByteArray("Basic") << QByteArray("Basic"); + QTest::newRow("ntlm-basic+param-ntlm") << QByteArray("NTLM , Basic realm=foo, bar = baz, NTLM") << QByteArray("NTLM") << QByteArray("NTLM"); + QTest::newRow("ntlm-with-type{2|3}") << QByteArray("NTLM VFlQRV8yX09SXzNfTUVTU0FHRQo=") << QByteArray("NTLM") << QByteArray("NTLM VFlQRV8yX09SXzNfTUVTU0FHRQo="); + + // Unknown schemes always return blank, i.e. auth request should be ignored + QTest::newRow("unknown-param") << QByteArray("Newauth realm=\"newauth\"") << QByteArray() << QByteArray(); + QTest::newRow("unknown-unknown") << QByteArray("NewAuth , NewAuth2") << QByteArray() << QByteArray(); +} + +void HTTPAuthenticationTest::testAuthenticationSelection() +{ + QFETCH(QByteArray, input); + QFETCH(QByteArray, expectedScheme); + QFETCH(QByteArray, expectedOffer); + + QByteArray scheme, offer; + parseAuthHeader(input, &offer, &scheme, 0); + QCOMPARE(scheme, expectedScheme); + QCOMPARE(offer, expectedOffer); +} + +void HTTPAuthenticationTest::testAuthentication_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expectedResponse"); + QTest::addColumn("user"); + QTest::addColumn("pass"); + QTest::addColumn("url"); + QTest::addColumn("cnonce"); + + // Test cases from RFC 2617... + QTest::newRow("rfc-2617-basic-example") + << QByteArray("Basic realm=\"WallyWorld\"") + << QByteArray("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==") + << QByteArray("Aladdin") + << QByteArray("open sesame") + << QByteArray() + << QByteArray(); + QTest::newRow("rfc-2617-digest-example") + << QByteArray("Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"") + << QByteArray("Digest username=\"Mufasa\", realm=\"testrealm@host.com\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", uri=\"/dir/index.html\", algorithm=MD5, qop=auth, cnonce=\"0a4f113b\", nc=00000001, response=\"6629fae49393a05397450978507c4ef1\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"") + << QByteArray("Mufasa") + << QByteArray("Circle Of Life") + << QByteArray("http://www.nowhere.org/dir/index.html") + << QByteArray("0a4f113b"); + QTest::newRow("ntlm-negotiate-type1") + << QByteArray("NTLM") + << QByteArray("NTLM TlRMTVNTUAABAAAABQIAAAAAAAAAAAAAAAAAAAAAAAA=") + << QByteArray() + << QByteArray() + << QByteArray() + << QByteArray(); + QTest::newRow("ntlm-challenge-type2") + << QByteArray("NTLM TlRMTVNTUAACAAAAFAAUACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAFUAcgBzAGEALQBNAGEAagBvAHIA") + << QByteArray("NTLM TlRMTVNTUAADAAAAGAAYAFgAAAAYABgAQAAAABQAFABwAAAADAAMAIQAAAAWABYAkAAAAAAAAAAAAAAAAYIAAODgDeMQShvyBT8Hx92oLTxImumJ4bAA062Hym3v40aFucQ8R3qMQtYAZn1okufol1UAcgBzAGEALQBNAGkAbgBvAHIAWgBhAHAAaABvAGQAVwBPAFIASwBTAFQAQQBUAEkATwBOAA==") + << QByteArray("Ursa-Minor\\Zaphod") + << QByteArray("Beeblebrox") + << QByteArray() + << QByteArray(); + QTest::newRow("ntlm-challenge-type2-no-domain") + << QByteArray("NTLM TlRMTVNTUAACAAAAFAAUACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAFUAcgBzAGEALQBNAGEAagBvAHIA") + << QByteArray("NTLM TlRMTVNTUAADAAAAGAAYAFgAAAAYABgAQAAAABQAFABwAAAADAAMAIQAAAAWABYAkAAAAAAAAAAAAAAAAYIAAODgDeMQShvyBT8Hx92oLTxImumJ4bAA062Hym3v40aFucQ8R3qMQtYAZn1okufol1UAcgBzAGEALQBNAGEAagBvAHIAWgBhAHAAaABvAGQAVwBPAFIASwBTAFQAQQBUAEkATwBOAA==") + << QByteArray("Zaphod") + << QByteArray("Beeblebrox") + << QByteArray() + << QByteArray(); + QTest::newRow("ntlm-challenge-type2-empty-domain") + << QByteArray("NTLM TlRMTVNTUAACAAAAFAAUACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAFUAcgBzAGEALQBNAGEAagBvAHIA") + << QByteArray("NTLM TlRMTVNTUAADAAAAGAAYAFgAAAAYABgAQAAAAAAAAAAAAAAADAAMAHAAAAAWABYAfAAAAAAAAAAAAAAAAYIAAODgDeMQShvyBT8Hx92oLTxImumJ4bAA062Hym3v40aFucQ8R3qMQtYAZn1okufol1oAYQBwAGgAbwBkAFcATwBSAEsAUwBUAEEAVABJAE8ATgA=") + << QByteArray("\\Zaphod") + << QByteArray("Beeblebrox") + << QByteArray() + << QByteArray(); +} + +void HTTPAuthenticationTest::testAuthentication() +{ + QFETCH(QByteArray, input); + QFETCH(QByteArray, expectedResponse); + QFETCH(QByteArray, user); + QFETCH(QByteArray, pass); + QFETCH(QByteArray, url); + QFETCH(QByteArray, cnonce); + + QByteArray bestOffer; + parseAuthHeader(input, &bestOffer, 0, 0); + KAbstractHttpAuthentication *authObj = KAbstractHttpAuthentication::newAuth(bestOffer); + QVERIFY(authObj); + if (!cnonce.isEmpty()) { + authObj->setDigestNonceValue(cnonce); + } + authObj->setChallenge(bestOffer, QUrl(url), "GET"); + authObj->generateResponse(QString(user), QString(pass)); + QCOMPARE(authObj->headerFragment().trimmed().constData(), expectedResponse.constData()); + delete authObj; +} + +void HTTPAuthenticationTest::testAuthenticationNTLMv2() +{ + QByteArray input("NTLM TlRMTVNTUAACAAAABgAGADgAAAAFAokCT0wyUnb4OSQAAAAAAAAAAMYAxgA+AAAABgGxHQAAAA9UAFMAVAACAAYAVABTAFQAAQASAEQAVgBHAFIASwBWAFEAUABEAAQAKgB0AHMAdAAuAGQAagBrAGgAcQBjAGkAaABtAGMAbwBmAGoALgBvAHIAZwADAD4ARABWAEcAUgBLAFYAUQBQAEQALgB0AHMAdAAuAGQAagBrAGgAcQBjAGkAaABtAGMAbwBmAGoALgBvAHIAZwAFACIAZABqAGsAaABxAGMAaQBoAG0AYwBvAGYAagAuAG8AcgBnAAcACABvb9jXZl7RAQAAAAA="); + QByteArray expectedResponse("TlRMTVNTUAADAAAAGAAYADYBAAD2APYAQAAAAAYABgBOAQAABgAGAFQBAAAWABYAWgEAAAAAAAAAAAAABQKJArXyhsxZPveKcfcV21viIsUBAQAAAAAAAAC8GQxfX9EBTHOi1kJbHbQAAAAAAgAGAFQAUwBUAAEAEgBEAFYARwBSAEsAVgBRAFAARAAEACoAdABzAHQALgBkAGoAawBoAHEAYwBpAGgAbQBjAG8AZgBqAC4AbwByAGcAAwA+AEQAVgBHAFIASwBWAFEAUABEAC4AdABzAHQALgBkAGoAawBoAHEAYwBpAGgAbQBjAG8AZgBqAC4AbwByAGcABQAiAGQAagBrAGgAcQBjAGkAaABtAGMAbwBmAGoALgBvAHIAZwAHAAgAb2/Y12Ze0QEAAAAAAAAAAOInN0N/15GHBtz3WXvvV159KG/2MbYk0FQAUwBUAGIAbwBiAFcATwBSAEsAUwBUAEEAVABJAE8ATgA="); + QString user("TST\\bob"); + QString pass("cacamas"); + QString target("TST"); + + QByteArray bestOffer; + parseAuthHeader(input, &bestOffer, 0, 0); + KConfig conf; + KConfigGroup confGroup = conf.group("test"); + confGroup.writeEntry("EnableNTLMv2Auth", true); + KAbstractHttpAuthentication *authObj = KAbstractHttpAuthentication::newAuth(bestOffer, &confGroup); + QVERIFY(authObj); + + authObj->setChallenge(bestOffer, QUrl(), "GET"); + authObj->generateResponse(QString(user), QString(pass)); + + QByteArray resp(QByteArray::fromBase64(authObj->headerFragment().trimmed().mid(5))); + QByteArray expResp(QByteArray::fromBase64(expectedResponse)); + + /* Prepare responses stripped from any data that is variable. */ + QByteArray strippedResp(resp); + memset(strippedResp.data() + 0x40, 0, 0x10); // NTLMv2 MAC + memset(strippedResp.data() + 0x58, 0, 0x10); // timestamp + client nonce + memset(strippedResp.data() + 0x136, 0, 0x18); // LMv2 MAC + QByteArray strippedExpResp(expResp); + memset(strippedExpResp.data() + 0x40, 0, 0x10); // NTLMv2 MAC + memset(strippedExpResp.data() + 0x58, 0, 0x10); // timestamp + client nonce + memset(strippedExpResp.data() + 0x136, 0, 0x18); // LMv2 MAC + + /* Compare the stripped responses. */ + QCOMPARE(strippedResp.toBase64(), strippedExpResp.toBase64()); + + /* Verify the NTLMv2 response MAC. */ + QByteArray challenge(QByteArray::fromBase64(input.mid(5))); + QByteArray serverNonce(challenge.mid(0x18, 8)); + + QByteArray uniPass(QString2UnicodeLE(pass)); + QByteArray ntlmHash(QCryptographicHash::hash(uniPass, QCryptographicHash::Md4)); + int i = user.indexOf('\\'); + QString username; + if (i >= 0) { + username = user.mid(i + 1); + } + else { + username = user; + } + + QByteArray userTarget(QString2UnicodeLE(username.toUpper() + target)); + QByteArray ntlm2Hash(hmacMD5(userTarget, ntlmHash)); + QByteArray hashData(serverNonce + resp.mid(0x50, 230)); + QByteArray mac(hmacMD5(hashData, ntlm2Hash)); + + QCOMPARE(mac.toHex(), resp.mid(0x40, 16).toHex()); + + /* Verify the LMv2 response MAC. */ + QByteArray lmHashData(serverNonce + resp.mid(0x146, 8)); + QByteArray lmHash(hmacMD5(lmHashData, ntlm2Hash)); + + QCOMPARE(lmHash.toHex(), resp.mid(0x136, 16).toHex()); + + delete authObj; +} diff --git a/autotests/http/httpauthenticationtest.h b/autotests/http/httpauthenticationtest.h new file mode 100644 index 0000000..b6900b2 --- /dev/null +++ b/autotests/http/httpauthenticationtest.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (c) 2011 Dawit Alemayehu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +class HTTPAuthenticationTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testHeaderParsing(); + void testHeaderParsing_data(); + void testAuthenticationSelection(); + void testAuthenticationSelection_data(); + void testAuthentication(); + void testAuthentication_data(); + void testAuthenticationNTLMv2(); +}; diff --git a/autotests/http/httpfiltertest.cpp b/autotests/http/httpfiltertest.cpp new file mode 100644 index 0000000..b97a4a8 --- /dev/null +++ b/autotests/http/httpfiltertest.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2002-2005 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include "kfilterdev.h" +#include "kfilterbase.h" +#include +#include +#include +#include +#include "httpfilter.h" + +class HTTPFilterTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void test_deflateWithZlibHeader(); + void test_httpFilterGzip(); + +private: + void test_block_write(const QString &fileName, const QByteArray &data); + void test_block_read(const QString &fileName); + void test_getch(const QString &fileName); + void test_textstream(const QString &fileName); + void test_readall(const QString &fileName, const QString &mimeType, const QByteArray &expectedData); + +protected Q_SLOTS: + void slotFilterOutput(const QByteArray &data); + +private: + QString pathgz; + QByteArray testData; + QByteArray m_filterOutput; +}; + +QTEST_MAIN(HTTPFilterTest) + +void HTTPFilterTest::initTestCase() +{ + qRegisterMetaType(); + const QString currentdir = QDir::currentPath(); + pathgz = currentdir + "/test.gz"; + + testData = "hello world\n"; + + // Create the gz file + + KFilterDev dev(pathgz); + QVERIFY(dev.open(QIODevice::WriteOnly)); + const int ret = dev.write(testData); + QCOMPARE(ret, testData.size()); + dev.close(); +} + +static void getCompressedData(QByteArray &data, QByteArray &compressedData) +{ + data = "Hello world, this is a test for deflate, from bug 114830 / 117683"; + compressedData.resize(long(data.size() * 1.1f) + 12L); // requirements of zlib::compress2 + unsigned long out_bufferlen = compressedData.size(); + const int ret = compress2((Bytef *)compressedData.data(), &out_bufferlen, (const Bytef *)data.constData(), data.size(), 1); + QCOMPARE(ret, Z_OK); + compressedData.resize(out_bufferlen); +} + +void HTTPFilterTest::test_deflateWithZlibHeader() +{ + QByteArray data, deflatedData; + getCompressedData(data, deflatedData); + + { + HTTPFilterDeflate filter; + QSignalSpy spyOutput(&filter, SIGNAL(output(QByteArray))); + QSignalSpy spyError(&filter, SIGNAL(error(QString))); + filter.slotInput(deflatedData); + QCOMPARE(spyOutput.count(), 2); + QCOMPARE(spyOutput[0][0].toByteArray(), data); + QCOMPARE(spyOutput[1][0].toByteArray(), QByteArray()); + QCOMPARE(spyError.count(), 0); + } + { + // Now a test for giving raw deflate data to HTTPFilter + HTTPFilterDeflate filter; + QSignalSpy spyOutput(&filter, SIGNAL(output(QByteArray))); + QSignalSpy spyError(&filter, SIGNAL(error(QString))); + QByteArray rawDeflate = deflatedData.mid(2); // remove CMF+FLG + rawDeflate.truncate(rawDeflate.size() - 4); // remove trailing Adler32. + filter.slotInput(rawDeflate); + QCOMPARE(spyOutput.count(), 2); + QCOMPARE(spyOutput[0][0].toByteArray(), data); + QCOMPARE(spyOutput[1][0].toByteArray(), QByteArray()); + QCOMPARE(spyError.count(), 0); + } +} + +void HTTPFilterTest::test_httpFilterGzip() +{ + QFile file(pathgz); + QVERIFY(file.open(QIODevice::ReadOnly)); + const QByteArray compressed = file.readAll(); + + // Test sending the whole data in one go + { + HTTPFilterGZip filter; + QSignalSpy spyOutput(&filter, SIGNAL(output(QByteArray))); + QSignalSpy spyError(&filter, SIGNAL(error(QString))); + filter.slotInput(compressed); + QCOMPARE(spyOutput.count(), 2); + QCOMPARE(spyOutput[0][0].toByteArray(), testData); + QCOMPARE(spyOutput[1][0].toByteArray(), QByteArray()); + QCOMPARE(spyError.count(), 0); + } + + // Test sending the data byte by byte + { + m_filterOutput.clear(); + HTTPFilterGZip filter; + QSignalSpy spyOutput(&filter, SIGNAL(output(QByteArray))); + connect(&filter, SIGNAL(output(QByteArray)), this, SLOT(slotFilterOutput(QByteArray))); + QSignalSpy spyError(&filter, SIGNAL(error(QString))); + for (int i = 0; i < compressed.size(); ++i) { + //qDebug() << "sending byte number" << i << ":" << (uchar)compressed[i]; + filter.slotInput(QByteArray(compressed.constData() + i, 1)); + QCOMPARE(spyError.count(), 0); + } + QCOMPARE(m_filterOutput, testData); + QCOMPARE(spyOutput[spyOutput.count() - 1][0].toByteArray(), QByteArray()); // last one was empty + } +} + +void HTTPFilterTest::slotFilterOutput(const QByteArray &data) +{ + m_filterOutput += data; +} + +#include "httpfiltertest.moc" diff --git a/autotests/http/httpheaderdispositiontest.cpp b/autotests/http/httpheaderdispositiontest.cpp new file mode 100644 index 0000000..ac41656 --- /dev/null +++ b/autotests/http/httpheaderdispositiontest.cpp @@ -0,0 +1,382 @@ +/* This file is part of the KDE libraries + Copyright (C) 2010,2011 Rolf Eike Beer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "httpheaderdispositiontest.h" + +#include + +#include +#include + +#include + +#include + +// QT5 TODO QTEST_GUILESS_MAIN(HeaderDispositionTest) +QTEST_MAIN(HeaderDispositionTest) + +static void runTest(const QString &header, const QByteArray &result) +{ + QMap parameters = contentDispositionParser(header); + + QList results = result.split('\n'); + if (result.isEmpty()) { + results.clear(); + } + + foreach (const QByteArray &ba, results) { + QList values = ba.split('\t'); + const QString key(QString::fromLatin1(values.takeFirst())); + + QVERIFY(parameters.contains(key)); + + const QByteArray val = values.takeFirst(); + QVERIFY(values.isEmpty()); + + QCOMPARE(parameters[key], QString::fromUtf8(val.constData(), val.length())); + } + + QCOMPARE(parameters.count(), results.count()); +} + +void HeaderDispositionTest::runAllTests_data() +{ + QTest::addColumn("header"); + QTest::addColumn("result"); + + // http://greenbytes.de/tech/tc2231/ + QTest::newRow("greenbytes-inlonly") << "inline" << + QByteArray("type\tinline"); + QTest::newRow("greenbytes-inlonlyquoted") << "\"inline\"" << + QByteArray(); + QTest::newRow("greenbytes-inlwithasciifilename") << "inline; filename=\"foo.html\"" << + QByteArray("type\tinline\n" + "filename\tfoo.html"); + QTest::newRow("greenbytes-inlwithfnattach") << "inline; filename=\"Not an attachment!\"" << + QByteArray("type\tinline\n" + "filename\tNot an attachment!"); + QTest::newRow("greenbytes-inlwithasciifilenamepdf") << "inline; filename=\"foo.pdf\"" << + QByteArray("type\tinline\n" + "filename\tfoo.pdf"); + QTest::newRow("greenbytes-attonly") << "attachment" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attonlyquoted") << "\"attachment\"" << + QByteArray(); + QTest::newRow("greenbytes-attonlyucase") << "ATTACHMENT" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attwithasciifilename") << "attachment; filename=\"foo.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); + QTest::newRow("greenbytes-attwithasciifnescapedchar") << "attachment; filename=\"f\\oo.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); + QTest::newRow("greenbytes-attwithasciifnescapedquote") << "attachment; filename=\"\\\"quoting\\\" tested.html\"" << + QByteArray("type\tattachment\n" + "filename\t\"quoting\" tested.html"); + QTest::newRow("greenbytes-attwithquotedsemicolon") << "attachment; filename=\"Here's a semicolon;.html\"" << + QByteArray("type\tattachment\n" + "filename\tHere's a semicolon;.html"); + QTest::newRow("greenbytes-attwithfilenameandextparam") << "attachment; foo=\"bar\"; filename=\"foo.html\"" << + QByteArray("type\tattachment\n" + "foo\tbar\n" + "filename\tfoo.html"); + QTest::newRow("greenbytes-attwithfilenameandextparamescaped") << "attachment; foo=\"\\\"\\\\\";filename=\"foo.html\"" << + QByteArray("type\tattachment\n" + "foo\t\"\\\n" + "filename\tfoo.html"); + QTest::newRow("greenbytes-attwithasciifilenameucase") << "attachment; FILENAME=\"foo.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); +// specification bug in RfC 2616, legal through RfC 2183 and 6266 + QTest::newRow("greenbytes-attwithasciifilenamenq") << "attachment; filename=foo.html" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); + QTest::newRow("greenbytes-attwithasciifilenamenqws") << "attachment; filename=foo bar.html" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attwithfntokensq") << "attachment; filename='foo.bar'" << + QByteArray("type\tattachment\n" + "filename\t'foo.bar'"); + QTest::newRow("greenbytes-attwithisofnplain-x") << QStringLiteral("attachment; filename=\"foo-\xe4.html\"") << + QByteArray("type\tattachment\n" + "filename\tfoo-ä.html"); + QTest::newRow("greenbytes-attwithisofnplain") << QString::fromLatin1("attachment; filename=\"foo-ä.html\"") << + QByteArray("type\tattachment\n" + "filename\tfoo-ä.html"); + QTest::newRow("greenbytes-attwithfnrawpctenca") << "attachment; filename=\"foo-%41.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo-%41.html"); + QTest::newRow("greenbytes-attwithfnusingpct") << "attachment; filename=\"50%.html\"" << + QByteArray("type\tattachment\n" + "filename\t50%.html"); + QTest::newRow("greenbytes-attwithfnrawpctencaq") << "attachment; filename=\"foo-%\\41.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo-%41.html"); + QTest::newRow("greenbytes-attwithnamepct") << "attachment; name=\"foo-%41.html\"" << + QByteArray("type\tattachment\n" + "name\tfoo-%41.html"); + QTest::newRow("greenbytes-attwithfilenamepctandiso") << QStringLiteral("attachment; filename=\"\xe4-%41.html\"") << + QByteArray("type\tattachment\n" + "filename\tä-%41.html"); + QTest::newRow("greenbytes-attwithfnrawpctenclong") << "attachment; filename=\"foo-%c3%a4-%e2%82%ac.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo-%c3%a4-%e2%82%ac.html"); + QTest::newRow("greenbytes-attwithasciifilenamews1") << "attachment; filename =\"foo.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); + QTest::newRow("greenbytes-attwith2filenames") << "attachment; filename=\"foo.html\"; filename=\"bar.html\"" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attfnbrokentoken") << "attachment; filename=foo[1](2).html" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attmissingdisposition") << "filename=foo.html" << + QByteArray(); + QTest::newRow("greenbytes-attmissingdisposition2") << "x=y; filename=foo.html" << + QByteArray(); + QTest::newRow("greenbytes-attmissingdisposition3") << "\"foo; filename=bar;baz\"; filename=qux" << + QByteArray(); + QTest::newRow("greenbytes-attmissingdisposition4") << "filename=foo.html, filename=bar.html" << + QByteArray(); + QTest::newRow("greenbytes-emptydisposition") << "; filename=foo.html" << + QByteArray(); + QTest::newRow("greenbytes-attbrokenquotedfn") << "attachment; filename=\"foo.html\".txt" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attbrokenquotedfn2") << "attachment; filename=\"bar" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attbrokenquotedfn3") << "attachment; filename=foo\"bar;baz\"qux" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attreversed") << "filename=foo.html; attachment" << + QByteArray(); + QTest::newRow("greenbytes-attconfusedparam") << "attachment; xfilename=foo.html" << + QByteArray("type\tattachment\n" + "xfilename\tfoo.html"); + QTest::newRow("greenbytes-attabspath") << "attachment; filename=\"/foo.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); +#ifdef Q_OS_WIN + QTest::newRow("greenbytes-attabspath") << "attachment; filename=\"\\\\foo.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); +#else // Q_OS_WIN + QTest::newRow("greenbytes-attabspath") << "attachment; filename=\"\\\\foo.html\"" << + QByteArray("type\tattachment\n" + "filename\t\\foo.html"); +#endif // Q_OS_WIN + QTest::newRow("greenbytes-") << "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"" << + QByteArray("type\tattachment\n" + "creation-date\tWed, 12 Feb 1997 16:29:51 -0500"); + QTest::newRow("greenbytes-") << "attachment; modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"" << + QByteArray("type\tattachment\n" + "modification-date\tWed, 12 Feb 1997 16:29:51 -0500"); + QTest::newRow("greenbytes-dispext") << "foobar" << + QByteArray("type\tfoobar"); + QTest::newRow("greenbytes-dispextbadfn") << "attachment; example=\"filename=example.txt\"" << + QByteArray("type\tattachment\n" + "example\tfilename=example.txt"); + QTest::newRow("greenbytes-attwithisofn2231iso") << "attachment; filename*=iso-8859-1''foo-%E4.html" << + QByteArray("type\tattachment\n" + "filename\tfoo-ä.html"); + QTest::newRow("greenbytes-attwithfn2231utf8") << "attachment; filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html" << + QByteArray("type\tattachment\n" + "filename\tfoo-ä-€.html"); + QTest::newRow("greenbytes-attwithfn2231noc") << "attachment; filename*=''foo-%c3%a4-%e2%82%ac.html" << + QByteArray("type\tattachment"); +// it's not filename, but "filename " + QTest::newRow("greenbytes-attwithfn2231ws1") << "attachment; filename *=UTF-8''foo-%c3%a4.html" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attwithfn2231ws2") << "attachment; filename*= UTF-8''foo-%c3%a4.html" << + QByteArray("type\tattachment\n" + "filename\tfoo-ä.html"); + QTest::newRow("greenbytes-attwithfn2231ws3") << "attachment; filename* =UTF-8''foo-%c3%a4.html" << + QByteArray("type\tattachment\n" + "filename\tfoo-ä.html"); +// argument must not be enclosed in double quotes + QTest::newRow("greenbytes-attwithfn2231quot") << "attachment; filename*=\"UTF-8''foo-%c3%a4.html\"" << + QByteArray("type\tattachment"); + QTest::newRow("greenbytes-attwithfn2231dpct") << "attachment; filename*=UTF-8''A-%2541.html" << + QByteArray("type\tattachment\n" + "filename\tA-%41.html"); +#ifdef Q_OS_WIN + QTest::newRow("greenbytes-attwithfn2231abspathdisguised") << "attachment; filename*=UTF-8''%5cfoo.html" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); +#else // Q_OS_WIN + QTest::newRow("greenbytes-attwithfn2231abspathdisguised") << "attachment; filename*=UTF-8''%5cfoo.html" << + QByteArray("type\tattachment\n" + "filename\t\\foo.html"); +#endif // Q_OS_WIN + QTest::newRow("greenbytes-attfncont") << "attachment; filename*0=\"foo.\"; filename*1=\"html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); + QTest::newRow("greenbytes-attfncontenc") << "attachment; filename*0*=UTF-8''foo-%c3%a4; filename*1=\".html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo-ä.html"); +// no leading zeros + QTest::newRow("greenbytes-attfncontlz") << "attachment; filename*0=\"foo\"; filename*01=\"bar\"" << + QByteArray("type\tattachment\n" + "filename\tfoo"); + QTest::newRow("greenbytes-attfncontnc") << "attachment; filename*0=\"foo\"; filename*2=\"bar\"" << + QByteArray("type\tattachment\n" + "filename\tfoo"); +// first element must have number 0 + QTest::newRow("greenbytes-attfnconts1") << "attachment; filename*1=\"foo.\"; filename*2=\"html\"" << + QByteArray("type\tattachment"); +// we must not rely on element ordering + QTest::newRow("greenbytes-attfncontord") << "attachment; filename*1=\"bar\"; filename*0=\"foo\"" << + QByteArray("type\tattachment\n" + "filename\tfoobar"); +// specifying both param and param* is allowed, param* should be taken + QTest::newRow("greenbytes-attfnboth") << "attachment; filename=\"foo-ae.html\"; filename*=UTF-8''foo-%c3%a4.html" << + QByteArray("type\tattachment\n" + "filename\tfoo-ä.html"); +// specifying both param and param* is allowed, param* should be taken + QTest::newRow("greenbytes-attfnboth2") << "attachment; filename*=UTF-8''foo-%c3%a4.html; filename=\"foo-ae.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo-ä.html"); + QTest::newRow("greenbytes-attnewandfn") << "attachment; foobar=x; filename=\"foo.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html\n" + "foobar\tx"); +// invalid argument, should be ignored + QTest::newRow("greenbytes-attrfc2047token") << "attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=" << + QByteArray("type\tattachment"); + QTest::newRow("space_before_value") << "attachment; filename= \"foo.html\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); +// no character set given but 8 bit characters + QTest::newRow("8bit_in_ascii") << "attachment; filename*=''foo-%c3%a4.html" << + QByteArray("type\tattachment"); +// there may not be gaps in numbering + QTest::newRow("continuation013") << "attachment; filename*0=\"foo.\"; filename*1=\"html\"; filename*3=\"bar\"" << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); +// "wrong" element ordering and encoding + QTest::newRow("reversed_continuation+encoding") << "attachment; filename*1=\"html\"; filename*0*=us-ascii''foo." << + QByteArray("type\tattachment\n" + "filename\tfoo.html"); +// unknown charset + QTest::newRow("unknown_charset") << "attachment; filename*=unknown''foo" << + QByteArray("type\tattachment"); +// no apostrophs + QTest::newRow("encoding-no-apostrophs") << "attachment; filename*=foo" << + QByteArray("type\tattachment"); +// only one apostroph + QTest::newRow("encoding-one-apostroph") << "attachment; filename*=us-ascii'foo" << + QByteArray("type\tattachment"); +// duplicate filename, both should be ignored and parsing should stop + QTest::newRow("duplicate-filename") << "attachment; filename=foo; filename=bar; foo=bar" << + QByteArray("type\tattachment"); +// garbage after closing quote, parsing should stop there + QTest::newRow("garbage_after_closing_quote") << "attachment; filename*=''foo; bar=\"f\"oo; baz=foo" << + QByteArray("type\tattachment\n" + "filename\tfoo"); +// trailing whitespace should be ignored + QTest::newRow("whitespace_after_value") << "attachment; filename=\"foo\" ; bar=baz" << + QByteArray("type\tattachment\n" + "filename\tfoo\n" + "bar\tbaz"); +// invalid syntax for type + QTest::newRow("invalid_type1") << "filename=foo.html" << + QByteArray(); +// invalid syntax for type + QTest::newRow("invalid_type2") << "inline{; filename=\"foo\"" << + QByteArray(); + QTest::newRow("invalid_type3") << "foo bar; filename=\"foo\"" << + QByteArray(); + QTest::newRow("invalid_type4") << "foo\tbar; filename=\"foo\"" << + QByteArray(); +// missing closing quote, so parameter is broken + QTest::newRow("no_closing_quote") << "attachment; filename=\"bar" << + QByteArray("type\tattachment"); +// we ignore any path given in the header and use only the filename + QTest::newRow("full_path_given") << "attachment; filename=\"/etc/shadow\"" << + QByteArray("type\tattachment\n" + "filename\tshadow"); +// we ignore any path given in the header and use only the filename even if there is an error later + QTest::newRow("full_path_and_parse_error") << "attachment; filename=\"/etc/shadow\"; foo=\"baz\"; foo=\"bar\"" << + QByteArray("type\tattachment\n" + "filename\tshadow"); +// control characters are forbidden in the quoted string + QTest::newRow("control_character_in_value") << "attachment; filename=\"foo\003\"" << + QByteArray("type\tattachment"); +// duplicate keys must be ignored + QTest::newRow("duplicate_with_continuation") << "attachment; filename=\"bar\"; filename*0=\"foo.\"; filename*1=\"html\"" << + QByteArray("type\tattachment"); + +// percent encoding, invalid first character + QTest::newRow("percent-first-char-invalid") << "attachment; filename*=UTF-8''foo-%o5.html" << + QByteArray("type\tattachment"); +// percent encoding, invalid second character + QTest::newRow("percent-second-char-invalid") << "attachment; filename*=UTF-8''foo-%5o.html" << + QByteArray("type\tattachment"); +// percent encoding, both characters invalid + QTest::newRow("greenbytes-attwithfn2231nbadpct2") << "attachment; filename*=UTF-8''foo-%oo.html" << + QByteArray("type\tattachment"); +// percent encoding, invalid second character + QTest::newRow("percent-second-char-missing") << "attachment; filename*=UTF-8''foo-%f.html" << + QByteArray("type\tattachment"); +// percent encoding, too short value + QTest::newRow("percent-short-encoding-at-end") << "attachment; filename*=UTF-8''foo-%f" << + QByteArray("type\tattachment"); +} + +#if 0 +// currently unclear if our behaviour is only accidentially correct +// invalid syntax +{ "inline; attachment; filename=foo.html", + "type\tinline" +}, +// invalid syntax +{ + "attachment; inline; filename=foo.html", + "type\tattachment" +}, + +// deactivated for now: failing due to missing implementation +{ + "attachment; filename=\"foo-ä.html\"", + "type\tattachment\n" + "filename\tfoo-ä.html" +}, +// deactivated for now: not the same utf, no idea if the expected value is actually correct +{ + "attachment; filename*=UTF-8''foo-a%cc%88.html", + "type\tattachment\n" + "filename\tfoo-ä.html" +} + +// deactivated for now: only working to missing implementation +// string is not valid iso-8859-1 so filename should be ignored +//"attachment; filename*=iso-8859-1''foo-%c3%a4-%e2%82%ac.html", +//"type\tattachment", + +// deactivated for now: only working to missing implementation +// should not be decoded +//"attachment; filename=\"=?ISO-8859-1?Q?foo-=E4.html?=\"", +//"type\tattachment\n" +//"filename\t=?ISO-8859-1?Q?foo-=E4.html?=", + +}; +#endif + +void HeaderDispositionTest::runAllTests() +{ + QFETCH(QString, header); + QFETCH(QByteArray, result); + + runTest(header, result); +} diff --git a/autotests/http/httpheaderdispositiontest.h b/autotests/http/httpheaderdispositiontest.h new file mode 100644 index 0000000..383ccf0 --- /dev/null +++ b/autotests/http/httpheaderdispositiontest.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (c) 2010 Rolf Eike Beer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef HTTPHEADERDISPOSITIONTEST_H +#define HTTPHEADERDISPOSITIONTEST_H + +#include + +class HeaderDispositionTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void runAllTests(); + void runAllTests_data(); +}; + +#endif //HTTPHEADERDISPOSITIONTEST_H diff --git a/autotests/http/httpheadertokenizetest.cpp b/autotests/http/httpheadertokenizetest.cpp new file mode 100644 index 0000000..ba01916 --- /dev/null +++ b/autotests/http/httpheadertokenizetest.cpp @@ -0,0 +1,196 @@ +/* This file is part of the KDE libraries + Copyright (c) 2008 Andreas Hartmetz + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "httpheadertokenizetest.h" + +#include + +#include +#include +#include + +#include + +#include + +// QT5 TODO QTEST_GUILESS_MAIN(HeaderTokenizeTest) +QTEST_MAIN(HeaderTokenizeTest) + +/* some possible fields that can be used for test headers + {"accept-ranges", false}, + {"cache-control", true}, + {"connection", true}, + {"content-disposition", false}, //is multi-valued in a way, but with ";" separator! + {"content-encoding", true}, + {"content-language", true}, + {"content-length", false}, + {"content-location", false}, + {"content-md5", false}, + {"content-type", false}, + {"date", false}, + {"dav", true}, //RFC 2518 + {"etag", false}, + {"expires", false}, + {"keep-alive", false}, //RFC 2068 + {"last-modified", false}, + {"link", false}, //RFC 2068, multi-valued with ";" separator + {"location", false}, +*/ + +//no use testing many different headers, just a couple each of the multi-valued +//and the single-valued group to make sure that corner cases work both if there +//are already entries for the header and if there are no entries. +static const char messyHeader[] = + "\n" + "accept-ranges:foo\r\n" + "connection: one\r\n" + " t_\r\n" + " wo,\r\n" + "\tthree\r\n" + "accept-ranges:42\n" + "accept-Ranges:\tmaybe \r" + " or not\n" + "CoNNectIoN:four, , ,, , \r\n" + " :fi:ve\r\n" + ":invalid stuff\r\n" + "\tinvalid: connection:close\t\r" + "connection: Six, seven ,, , eight\r" //one malformed newline... + "\n\r "; //two malformed newlines; end of header. also observe the trailing space. + +//tab separates values, newline separates header lines. the first word is the key. +static const char messyResult[] = + "accept-ranges\tfoo\t42\tmaybe or not\n" + "connection\tone t_ wo\tthree\tfour\t:fi:ve\tSix\tseven\teight"; + +static const char redirectHeader[] = +//"HTTP/1.1 302 Moved Temporarily\r\n" + "Location: http://www.hertz.de/rentacar/index.jsp?bsc=t&targetPage=reservationOnHomepage.jsp\r\n" + "Connection:close\r\n" + "Cache-Control: no-cache\r\n" + "Pragma: no-cache\r\n" + "\r\n"; + +static const char redirectResult[] = + "cache-control\tno-cache\n" + "connection\tclose\n" + "location\thttp://www.hertz.de/rentacar/index.jsp?bsc=t&targetPage=reservationOnHomepage.jsp\n" + "pragma\tno-cache"; + +static const int bufSize = 4096; +char buffer[bufSize]; + +void HeaderTokenizeTest::testMessyHeader() +{ + //Copy the header into a writable buffer + for (int i = 0; i < bufSize; i++) { + buffer[i] = 0; + } + strcpy(buffer, messyHeader); + + HeaderTokenizer tokenizer(buffer); + int tokenizeEnd = tokenizer.tokenize(0, strlen(messyHeader)); + QCOMPARE(tokenizeEnd, (int)(strlen(messyHeader) - 1)); + + // If the output of the tokenizer contains all the terms that should be there and + // exactly the number of terms that should be there then it's exactly correct. + // We are lax wrt trailing whitespace, by the way. It does neither explicitly matter + // nor not matter according to the standard. Internal whitespace similarly should not + // matter but we have to be exact because the tokenizer does not move strings around, + // it only overwrites \r and \n in case of line continuations. + + typedef QPair intPair; //foreach is a macro and does not like commas + + int nValues = 0; + foreach (const QByteArray &ba, QByteArray(messyResult).split('\n')) { + QList values = ba.split('\t'); + QByteArray key = values.takeFirst(); + nValues += values.count(); + + QList comparisonValues; + foreach (intPair be, tokenizer.value(key).beginEnd) { + comparisonValues.append(QByteArray(buffer + be.first, be.second - be.first)); + } + + QCOMPARE(comparisonValues.count(), values.count()); + for (int i = 0; i < values.count(); i++) { + QVERIFY(comparisonValues[i].startsWith(values[i])); + } + } + + int nValues2 = 0; + HeaderTokenizer::ConstIterator it = tokenizer.constBegin(); + for (; it != tokenizer.constEnd(); ++it) { + nValues2 += it.value().beginEnd.count(); + } + QCOMPARE(nValues2, nValues); + + return; //comment out for parsed header dump to stdout + + it = tokenizer.constBegin(); + for (; it != tokenizer.constEnd(); ++it) { + if (!it.value().beginEnd.isEmpty()) { + qDebug() << it.key() << ":"; + } + foreach (intPair be, it.value().beginEnd) { + qDebug() << " " << QByteArray(buffer + be.first, be.second - be.first); + } + } +} + +void HeaderTokenizeTest::testRedirectHeader() +{ + //Copy the header into a writable buffer + for (int i = 0; i < bufSize; i++) { + buffer[i] = 0; + } + strcpy(buffer, redirectHeader); + + HeaderTokenizer tokenizer(buffer); + int tokenizeEnd = tokenizer.tokenize(0, strlen(redirectHeader)); + QCOMPARE(tokenizeEnd, (int)strlen(redirectHeader)); + + typedef QPair intPair; + + int nValues = 0; + foreach (const QByteArray &ba, QByteArray(redirectResult).split('\n')) { + QList values = ba.split('\t'); + QByteArray key = values.takeFirst(); + nValues += values.count(); + + QList comparisonValues; + foreach (intPair be, tokenizer.value(key).beginEnd) { + comparisonValues.append(QByteArray(buffer + be.first, be.second - be.first)); + } + + QCOMPARE(comparisonValues.count(), values.count()); + for (int i = 0; i < values.count(); i++) { + QVERIFY(comparisonValues[i].startsWith(values[i])); + } + } + + int nValues2 = 0; + HeaderTokenizer::ConstIterator it = tokenizer.constBegin(); + for (; it != tokenizer.constEnd(); ++it) { + nValues2 += it.value().beginEnd.count(); + } + QCOMPARE(nValues2, nValues); + + // Fix compiler warning + (void)contentDispositionParser; +} diff --git a/autotests/http/httpheadertokenizetest.h b/autotests/http/httpheadertokenizetest.h new file mode 100644 index 0000000..e8690da --- /dev/null +++ b/autotests/http/httpheadertokenizetest.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (c) 2008 Andreas Hartmetz + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef HTTPHEADERTOKENIZETEST_H +#define HTTPHEADERTOKENIZETEST_H + +#include + +class HeaderTokenizeTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testMessyHeader(); + void testRedirectHeader(); +}; + +#endif //HTTPHEADERTOKENIZETEST_H diff --git a/autotests/http/httpobjecttest.cpp b/autotests/http/httpobjecttest.cpp new file mode 100644 index 0000000..466ae63 --- /dev/null +++ b/autotests/http/httpobjecttest.cpp @@ -0,0 +1,52 @@ +/* This file is part of the KDE libraries + Copyright (C) 2012 Rolf Eike Beer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "httpobjecttest.h" + +#include + +#include + +QTEST_MAIN(HeaderObjectTest) + +static void runTest() +{ + TestHTTPProtocol protocol("http", QByteArray(), "local://"); + + protocol.testParseContentDisposition(QStringLiteral("inline; filename=\"foo.pdf\"")); +} + +void HeaderObjectTest::runAllTests() +{ + runTest(); +} + +TestHTTPProtocol::TestHTTPProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app) + : HTTPProtocol(protocol, pool, app) +{ +} + +TestHTTPProtocol::~TestHTTPProtocol() +{ +} + +void TestHTTPProtocol::testParseContentDisposition(const QString &disposition) +{ + parseContentDisposition(disposition); +} diff --git a/autotests/http/httpobjecttest.h b/autotests/http/httpobjecttest.h new file mode 100644 index 0000000..8814ab1 --- /dev/null +++ b/autotests/http/httpobjecttest.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE libraries + Copyright (c) 2012 Rolf Eike Beer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef HTTPOBJECTTEST_H +#define HTTPOBJECTTEST_H + +#include +#include + +class HeaderObjectTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void runAllTests(); +}; + +class TestHTTPProtocol : public HTTPProtocol +{ + Q_OBJECT +public: + TestHTTPProtocol(const QByteArray &protocol, const QByteArray &pool, + const QByteArray &app); + virtual ~TestHTTPProtocol(); + + void testParseContentDisposition(const QString &disposition); +}; + +#endif //HTTPOBJECTTEST_H diff --git a/autotests/http_jobtest.cpp b/autotests/http_jobtest.cpp new file mode 100644 index 0000000..9010c26 --- /dev/null +++ b/autotests/http_jobtest.cpp @@ -0,0 +1,111 @@ +/* This file is part of the KDE libraries + Copyright (c) 2016 David Faure + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include +#include + +#include "httpserver_p.h" + +class HTTPJobTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testBasicGet(); + void testErrorPage(); + void testMimeTypeDetermination(); +}; + +void HTTPJobTest::initTestCase() +{ + QStandardPaths::setTestModeEnabled(true); + + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + // To let ctest exit, we shouldn't start kio_http_cache_cleaner + qputenv("KIO_DISABLE_CACHE_CLEANER", "yes"); +} + +void HTTPJobTest::testBasicGet() +{ + static const char response[] = "Hello world"; + HttpServerThread server(response, HttpServerThread::Public); + KIO::StoredTransferJob *job = KIO::storedGet(QUrl(server.endPoint())); + job->setUiDelegate(0); + QVERIFY(job->exec()); + QCOMPARE(QString::fromLatin1(job->data()), QString::fromLatin1(response)); +} + +void HTTPJobTest::testErrorPage() +{ + static const char response[] = "This is a response\nFile not found"; + HttpServerThread server(response, HttpServerThread::Error404); + server.setContentType("text/html"); + + // First we get an error page + KIO::StoredTransferJob *job = KIO::storedGet(QUrl(server.endPoint())); + job->setUiDelegate(0); + QVERIFY(job->exec()); + QCOMPARE(QString::fromLatin1(job->data()), QString::fromLatin1(response)); + QVERIFY(job->isErrorPage()); + QCOMPARE(job->error(), 0); + + // Second we disable error page, and get the actual job error + job = KIO::storedGet(QUrl(server.endPoint())); + job->setUiDelegate(0); + job->addMetaData(QStringLiteral("errorPage"), QStringLiteral("false")); // maybe this should be a proper setter... + QVERIFY(!job->exec()); + QVERIFY(!job->isErrorPage()); + QCOMPARE(job->error(), int(KIO::ERR_DOES_NOT_EXIST)); + + // To check that kio_http did read and discard the body correctly, do another working download. + server.setResponseData("Some HTML page here"); + server.setFeatures(HttpServerThread::Public); + server.setContentType(""); + job = KIO::storedGet(QUrl(server.endPoint())); + job->setUiDelegate(0); + QSignalSpy mimeTypeSpy(job, SIGNAL(mimetype(KIO::Job*,QString))); + QVERIFY(job->exec()); + QCOMPARE(job->error(), 0); + QCOMPARE(mimeTypeSpy.count(), 1); + QCOMPARE(mimeTypeSpy.at(0).at(1).toString(), QStringLiteral("text/html")); +} + +void HTTPJobTest::testMimeTypeDetermination() +{ + static const char response[] = "Some HTML page here"; + HttpServerThread server(response, HttpServerThread::Public); + // Add a trailing slash to ensure kio_http doesn't confuse QMimeDatabase with it. + KIO::StoredTransferJob *job = KIO::storedGet(QUrl(server.endPoint() + '/')); + job->setUiDelegate(0); + QSignalSpy mimeTypeSpy(job, SIGNAL(mimetype(KIO::Job*,QString))); + QVERIFY(job->exec()); + QCOMPARE(job->error(), 0); + QCOMPARE(mimeTypeSpy.count(), 1); + QCOMPARE(mimeTypeSpy.at(0).at(1).toString(), QStringLiteral("text/html")); +} + +QTEST_MAIN(HTTPJobTest) +#include "http_jobtest.moc" diff --git a/autotests/httpserver_p.cpp b/autotests/httpserver_p.cpp new file mode 100644 index 0000000..5497764 --- /dev/null +++ b/autotests/httpserver_p.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** Copyright (C) 2010-2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com. +** Author: David Faure +** All rights reserved. +** +** This file initially comes from the KD Soap library. +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2.1 and version 3 as published by the +** Free Software Foundation and appearing in the file COPYING.LIB included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "httpserver_p.h" +#include +#include +#include +#include +#include + +static bool splitHeadersAndData(const QByteArray &request, QByteArray &header, QByteArray &data) +{ + const int sep = request.indexOf("\r\n\r\n"); + if (sep <= 0) { + return false; + } + header = request.left(sep); + data = request.mid(sep + 4); + return true; +} + +typedef QMap HeadersMap; +static HeadersMap parseHeaders(const QByteArray &headerData) +{ + HeadersMap headersMap; + QBuffer sourceBuffer; + sourceBuffer.setData(headerData); + sourceBuffer.open(QIODevice::ReadOnly); + // The first line is special, it's the GET or POST line + const QList firstLine = sourceBuffer.readLine().split(' '); + if (firstLine.count() < 3) { + qDebug() << "Malformed HTTP request:" << firstLine; + return headersMap; + } + const QByteArray request = firstLine[0]; + const QByteArray path = firstLine[1]; + const QByteArray httpVersion = firstLine[2]; + if (request != "GET" && request != "POST") { + qDebug() << "Unknown HTTP request:" << firstLine; + return headersMap; + } + headersMap.insert("_path", path); + headersMap.insert("_httpVersion", httpVersion); + + while (!sourceBuffer.atEnd()) { + const QByteArray line = sourceBuffer.readLine(); + const int pos = line.indexOf(':'); + if (pos == -1) { + qDebug() << "Malformed HTTP header:" << line; + } + const QByteArray header = line.left(pos); + const QByteArray value = line.mid(pos + 1).trimmed(); // remove space before and \r\n after + //qDebug() << "HEADER" << header << "VALUE" << value; + headersMap.insert(header, value); + } + return headersMap; +} + +enum Method { None, Basic, Plain, Login, Ntlm, CramMd5, DigestMd5 }; + +static void parseAuthLine(const QString &str, Method *method, QString *headerVal) +{ + *method = None; + // The code below (from QAuthenticatorPrivate::parseHttpResponse) + // is supposed to be run in a loop, apparently + // (multiple WWW-Authenticate lines? multiple values in the line?) + + //qDebug() << "parseAuthLine() " << str; + if (*method < Basic && str.startsWith(QLatin1String("Basic"), Qt::CaseInsensitive)) { + *method = Basic; + *headerVal = str.mid(6); + } else if (*method < Ntlm && str.startsWith(QLatin1String("NTLM"), Qt::CaseInsensitive)) { + *method = Ntlm; + *headerVal = str.mid(5); + } else if (*method < DigestMd5 && str.startsWith(QLatin1String("Digest"), Qt::CaseInsensitive)) { + *method = DigestMd5; + *headerVal = str.mid(7); + } +} + +QByteArray HttpServerThread::makeHttpResponse(const QByteArray &responseData) const +{ + QByteArray httpResponse; + if (m_features & Error404) { + httpResponse += "HTTP/1.1 404 Not Found\r\n"; + } else { + httpResponse += "HTTP/1.1 200 OK\r\n"; + } + if (!m_contentType.isEmpty()) { + httpResponse += "Content-Type: " + m_contentType + "\r\n"; + } + httpResponse += "Mozilla/5.0 (X11; Linux x86_64) KHTML/5.20.0 (like Gecko) Konqueror/5.20\r\n"; + httpResponse += "Content-Length: "; + httpResponse += QByteArray::number(responseData.size()); + httpResponse += "\r\n"; + + // We don't support multiple connexions so let's ask the client + // to close the connection every time. + httpResponse += "Connection: close\r\n"; + httpResponse += "\r\n"; + httpResponse += responseData; + return httpResponse; +} + +void HttpServerThread::disableSsl() +{ + m_server->disableSsl(); +} + +void HttpServerThread::finish() +{ + KIO::Job *job = KIO::get(QUrl(endPoint() + QLatin1String("/terminateThread"))); + job->exec(); +} + +void HttpServerThread::run() +{ + m_server = new BlockingHttpServer(m_features & Ssl); + m_server->listen(); + QMutexLocker lock(&m_mutex); + m_port = m_server->serverPort(); + lock.unlock(); + m_ready.release(); + + const bool doDebug = qEnvironmentVariableIsSet("HTTP_TEST_DEBUG"); + + if (doDebug) { + qDebug() << "HttpServerThread listening on port" << m_port; + } + + // Wait for first connection (we'll wait for further ones inside the loop) + QTcpSocket *clientSocket = m_server->waitForNextConnectionSocket(); + Q_ASSERT(clientSocket); + + Q_FOREVER { + // get the "request" packet + if (doDebug) { + qDebug() << "HttpServerThread: waiting for read"; + } + if (clientSocket->state() == QAbstractSocket::UnconnectedState || + !clientSocket->waitForReadyRead(2000)) { + if (clientSocket->state() == QAbstractSocket::UnconnectedState) { + delete clientSocket; + if (doDebug) { + qDebug() << "Waiting for next connection..."; + } + clientSocket = m_server->waitForNextConnectionSocket(); + Q_ASSERT(clientSocket); + continue; // go to "waitForReadyRead" + } else { + qDebug() << "HttpServerThread:" << clientSocket->error() << "waiting for \"request\" packet"; + break; + } + } + const QByteArray request = m_partialRequest + clientSocket->readAll(); + if (doDebug) { + qDebug() << "HttpServerThread: request:" << request; + } + + // Split headers and request xml + lock.relock(); + const bool splitOK = splitHeadersAndData(request, m_receivedHeaders, m_receivedData); + if (!splitOK) { + //if (doDebug) + // qDebug() << "Storing partial request" << request; + m_partialRequest = request; + continue; + } + + m_headers = parseHeaders(m_receivedHeaders); + + if (m_headers.value("Content-Length").toInt() > m_receivedData.size()) { + //if (doDebug) + // qDebug() << "Storing partial request" << request; + m_partialRequest = request; + continue; + } + + m_partialRequest.clear(); + + if (m_headers.value("_path").endsWith("terminateThread")) { // we're asked to exit + break; // normal exit + } + + lock.unlock(); + + //qDebug() << "headers received:" << m_receivedHeaders; + //qDebug() << headers; + //qDebug() << "data received:" << m_receivedData; + + if (m_features & BasicAuth) { + QByteArray authValue = m_headers.value("Authorization"); + if (authValue.isEmpty()) { + authValue = m_headers.value("authorization"); // as sent by Qt-4.5 + } + bool authOk = false; + if (!authValue.isEmpty()) { + //qDebug() << "got authValue=" << authValue; // looks like "Basic " + Method method; + QString headerVal; + parseAuthLine(QString::fromLatin1(authValue.data(), authValue.size()), &method, &headerVal); + //qDebug() << "method=" << method << "headerVal=" << headerVal; + switch (method) { + case None: // we want auth, so reject "None" + break; + case Basic: { + const QByteArray userPass = QByteArray::fromBase64(headerVal.toLatin1()); + //qDebug() << userPass; + // TODO if (validateAuth(userPass)) { + if (userPass == ("kdab:testpass")) { + authOk = true; + } + break; + } + default: + qWarning("Unsupported authentication mechanism %s", authValue.constData()); + } + } + + if (!authOk) { + // send auth request (Qt supports basic, ntlm and digest) + const QByteArray unauthorized = "HTTP/1.1 401 Authorization Required\r\nWWW-Authenticate: Basic realm=\"example\"\r\nContent-Length: 0\r\n\r\n"; + clientSocket->write(unauthorized); + if (!clientSocket->waitForBytesWritten(2000)) { + qDebug() << "HttpServerThread:" << clientSocket->error() << "writing auth request"; + break; + } + continue; + } + } + + // send response + const QByteArray response = makeHttpResponse(m_dataToSend); + if (doDebug) { + qDebug() << "HttpServerThread: writing" << response; + } + clientSocket->write(response); + + clientSocket->flush(); + } + // all done... + delete clientSocket; + delete m_server; + if (doDebug) { + qDebug() << "HttpServerThread terminated"; + } +} + + +void BlockingHttpServer::incomingConnection(qintptr socketDescriptor) +{ + if (doSsl) { + QSslSocket *serverSocket = new QSslSocket; + serverSocket->setParent(this); + serverSocket->setSocketDescriptor(socketDescriptor); + connect(serverSocket, SIGNAL(sslErrors(QList)), this, SLOT(slotSslErrors(QList))); + // TODO setupSslServer(serverSocket); + //qDebug() << "Created QSslSocket, starting server encryption"; + serverSocket->startServerEncryption(); + sslSocket = serverSocket; + // If startServerEncryption fails internally [and waitForEncrypted hangs], + // then this is how to debug it. + // A way to catch such errors is really missing in Qt.. + //qDebug() << "startServerEncryption said:" << sslSocket->errorString(); + bool ok = serverSocket->waitForEncrypted(); + Q_ASSERT(ok); + Q_UNUSED(ok); + } else { + QTcpServer::incomingConnection(socketDescriptor); + } +} diff --git a/autotests/httpserver_p.h b/autotests/httpserver_p.h new file mode 100644 index 0000000..0566d8b --- /dev/null +++ b/autotests/httpserver_p.h @@ -0,0 +1,179 @@ +/**************************************************************************** +** Copyright (C) 2010-2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com. +** Author: David Faure +** All rights reserved. +** +** This file initially comes from the KD Soap library. +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2.1 and version 3 as published by the +** Free Software Foundation and appearing in the file COPYING.LIB included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef HTTPSERVER_P_H +#define HTTPSERVER_P_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class BlockingHttpServer; + +class HttpServerThread : public QThread +{ + Q_OBJECT +public: + enum Feature { + Public = 0, // HTTP with no ssl and no authentication needed + Ssl = 1, // HTTPS + BasicAuth = 2, // Requires authentication + Error404 = 4 // Return "404 not found" + // bitfield, next item is 8 + }; + Q_DECLARE_FLAGS(Features, Feature) + + HttpServerThread(const QByteArray &dataToSend, Features features) + : m_dataToSend(dataToSend), m_features(features) + { + start(); + m_ready.acquire(); + + } + ~HttpServerThread() + { + finish(); + wait(); + } + + void setContentType(const QByteArray &mime) + { + QMutexLocker lock(&m_mutex); + m_contentType = mime; + } + + void setResponseData(const QByteArray &data) + { + QMutexLocker lock(&m_mutex); + m_dataToSend = data; + } + + void setFeatures(Features features) + { + QMutexLocker lock(&m_mutex); + m_features = features; + } + + void disableSsl(); + inline int serverPort() const + { + QMutexLocker lock(&m_mutex); + return m_port; + } + QString endPoint() const + { + return QString::fromLatin1("%1://127.0.0.1:%2/path") + .arg(QString::fromLatin1((m_features & Ssl) ? "https" : "http")) + .arg(serverPort()); + } + + void finish(); + + QByteArray receivedData() const + { + QMutexLocker lock(&m_mutex); + return m_receivedData; + } + QByteArray receivedHeaders() const + { + QMutexLocker lock(&m_mutex); + return m_receivedHeaders; + } + void resetReceivedBuffers() + { + QMutexLocker lock(&m_mutex); + m_receivedData.clear(); + m_receivedHeaders.clear(); + } + + QByteArray header(const QByteArray &value) const + { + QMutexLocker lock(&m_mutex); + return m_headers.value(value); + } + +protected: + /* \reimp */ void run(); + +private: + QByteArray makeHttpResponse(const QByteArray &responseData) const; + +private: + QByteArray m_partialRequest; + QSemaphore m_ready; + QByteArray m_dataToSend; + QByteArray m_contentType; + + mutable QMutex m_mutex; // protects the 4 vars below + QByteArray m_receivedData; + QByteArray m_receivedHeaders; + QMap m_headers; + int m_port; + + Features m_features; + BlockingHttpServer *m_server; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(HttpServerThread::Features) + +// A blocking http server (must be used in a thread) which supports SSL. +class BlockingHttpServer : public QTcpServer +{ + Q_OBJECT +public: + BlockingHttpServer(bool ssl) : doSsl(ssl), sslSocket(0) {} + ~BlockingHttpServer() {} + + QTcpSocket *waitForNextConnectionSocket() + { + if (!waitForNewConnection(20000)) { // 2000 would be enough, except in valgrind + return 0; + } + if (doSsl) { + Q_ASSERT(sslSocket); + return sslSocket; + } else { + //qDebug() << "returning nextPendingConnection"; + return nextPendingConnection(); + } + } + + void incomingConnection(qintptr socketDescriptor) Q_DECL_OVERRIDE; + + void disableSsl() + { + doSsl = false; + } + +private Q_SLOTS: + void slotSslErrors(const QList &errors) + { + qDebug() << "server-side: slotSslErrors" << sslSocket->errorString() << errors; + } +private: + bool doSsl; + QTcpSocket *sslSocket; +}; + +#endif /* HTTPSERVER_P_H */ diff --git a/autotests/jobguitest.cpp b/autotests/jobguitest.cpp new file mode 100644 index 0000000..81eb382 --- /dev/null +++ b/autotests/jobguitest.cpp @@ -0,0 +1,97 @@ +/* This file is part of the KDE project + Copyright (C) 2011 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include "kiotesthelper.h" // createTestFile etc. + +#include +#include + +static QString otherTmpDir() +{ +#ifdef Q_OS_WIN + return QDir::tempPath() + "/jobtest/"; +#else + // This one needs to be on another partition + return QStringLiteral("/tmp/jobtest/"); +#endif +} + +class JobGuiTest : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void initTestCase() + { + // Start with a clean base dir + cleanupTestCase(); + homeTmpDir(); // create it + if (!QFile::exists(otherTmpDir())) { + bool ok = QDir().mkdir(otherTmpDir()); + if (!ok) { + qFatal("Couldn't create %s", qPrintable(homeTmpDir())); + } + } + } + + void cleanupTestCase() + { + delDir(homeTmpDir()); + delDir(otherTmpDir()); + } + + void pasteFileToOtherPartition() + { + const QString filePath = homeTmpDir() + "fileFromHome"; + const QString dest = otherTmpDir() + "fileFromHome_copied"; + QFile::remove(dest); + createTestFile(filePath); + + QMimeData *mimeData = new QMimeData; + QUrl fileUrl = QUrl::fromLocalFile(filePath); + mimeData->setUrls(QList() << fileUrl); + QApplication::clipboard()->setMimeData(mimeData); + + KIO::Job *job = KIO::pasteClipboard(QUrl::fromLocalFile(otherTmpDir()), static_cast(0)); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + + QVERIFY(QFile::exists(dest)); + QVERIFY(QFile::exists(filePath)); // still there + } + +private: + static void delDir(const QString &pathOrUrl) + { + KIO::Job *job = KIO::del(QUrl::fromLocalFile(pathOrUrl), KIO::HideProgressInfo); + job->setUiDelegate(0); + job->exec(); + } + +}; + +QTEST_MAIN(JobGuiTest) + +#include "jobguitest.moc" diff --git a/autotests/jobremotetest.cpp b/autotests/jobremotetest.cpp new file mode 100644 index 0000000..3aee84e --- /dev/null +++ b/autotests/jobremotetest.cpp @@ -0,0 +1,389 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2006 David Faure + Copyright (C) 2008 Norbert Frese + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include "jobremotetest.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +//#include "kiotesthelper.h" // createTestFile etc. + +QTEST_MAIN(JobRemoteTest) + +QDateTime s_referenceTimeStamp; + +// The code comes partly from jobtest.cpp + +static QUrl remoteTmpUrl() +{ + QString customDir(qgetenv("KIO_JOBREMOTETEST_REMOTETMP")); + if (customDir.isEmpty()) { + return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/'); + } else { + // Could be a path or a URL + return QUrl::fromUserInput(customDir + '/'); + } +} + +static QString localTmpDir() +{ +#ifdef Q_OS_WIN + return QDir::tempPath() + "/jobremotetest/"; +#else + // This one needs to be on another partition + return QStringLiteral("/tmp/jobremotetest/"); +#endif +} + +static bool myExists(const QUrl &url) +{ + KIO::Job *job = KIO::stat(url, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); + job->setUiDelegate(0); + return job->exec(); +} + +static bool myMkdir(const QUrl &url) +{ + KIO::Job *job = KIO::mkdir(url, -1); + job->setUiDelegate(0); + return job->exec(); +} + +void JobRemoteTest::initTestCase() +{ + QStandardPaths::enableTestMode(true); + + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + + s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + + // Start with a clean base dir + cleanupTestCase(); + QUrl url = remoteTmpUrl(); + if (!myExists(url)) { + const bool ok = url.isLocalFile() ? QDir().mkpath(url.toLocalFile()) : myMkdir(url); + if (!ok) { + qFatal("couldn't create %s", qPrintable(url.toString())); + } + } + const bool ok = QDir().mkpath(localTmpDir()); + if (!ok) { + qFatal("couldn't create %s", qPrintable(localTmpDir())); + } +} + +static void delDir(const QUrl &pathOrUrl) +{ + KIO::Job *job = KIO::del(pathOrUrl, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->exec(); +} + +void JobRemoteTest::cleanupTestCase() +{ + delDir(remoteTmpUrl()); + delDir(QUrl::fromLocalFile(localTmpDir())); +} + +void JobRemoteTest::enterLoop() +{ + QEventLoop eventLoop; + connect(this, SIGNAL(exitLoop()), + &eventLoop, SLOT(quit())); + eventLoop.exec(QEventLoop::ExcludeUserInputEvents); +} + +///// + +void JobRemoteTest::putAndGet() +{ + QUrl u(remoteTmpUrl()); + u.setPath(u.path() + "putAndGetFile"); + KIO::TransferJob *job = KIO::put(u, 0600, KIO::Overwrite | KIO::HideProgressInfo); + QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds + job->setModificationTime(mtime); + job->setUiDelegate(0); + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotResult(KJob*))); + connect(job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), + this, SLOT(slotDataReq(KIO::Job*,QByteArray&))); + m_result = -1; + m_dataReqCount = 0; + enterLoop(); + QVERIFY(m_result == 0); // no error + + m_result = -1; + + KIO::StoredTransferJob *getJob = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); + getJob->setUiDelegate(0); + connect(getJob, SIGNAL(result(KJob*)), + this, SLOT(slotGetResult(KJob*))); + enterLoop(); + QCOMPARE(m_result, 0); // no error + QCOMPARE(m_data, QByteArray("This is a test for KIO::put()\n")); + //QCOMPARE( m_data.size(), 11 ); +} + +void JobRemoteTest::slotGetResult(KJob *job) +{ + m_result = job->error(); + m_data = static_cast(job)->data(); + emit exitLoop(); +} + +void JobRemoteTest::slotDataReq(KIO::Job *, QByteArray &data) +{ + // Really not the way you'd write a slotDataReq usually :) + switch (m_dataReqCount++) { + case 0: + data = "This is a test for "; + break; + case 1: + data = "KIO::put()\n"; + break; + case 2: + data = QByteArray(); + break; + } +} + +void JobRemoteTest::slotResult(KJob *job) +{ + m_result = job->error(); + emit exitLoop(); +} + +//// + +void JobRemoteTest::openFileWriting() +{ + m_rwCount = 0; + + QUrl u(remoteTmpUrl()); + u.setPath(u.path() + "openFileWriting"); + fileJob = KIO::open(u, QIODevice::WriteOnly); + + fileJob->setUiDelegate(0); + connect(fileJob, SIGNAL(result(KJob*)), + this, SLOT(slotResult(KJob*))); + connect(fileJob, SIGNAL(data(KIO::Job*,QByteArray)), + this, SLOT(slotFileJobData(KIO::Job*,QByteArray))); + connect(fileJob, SIGNAL(open(KIO::Job*)), + this, SLOT(slotFileJobOpen(KIO::Job*))); + connect(fileJob, SIGNAL(written(KIO::Job*,KIO::filesize_t)), + this, SLOT(slotFileJobWritten(KIO::Job*,KIO::filesize_t))); + connect(fileJob, SIGNAL(position(KIO::Job*,KIO::filesize_t)), + this, SLOT(slotFileJobPosition(KIO::Job*,KIO::filesize_t))); + connect(fileJob, SIGNAL(close(KIO::Job*)), + this, SLOT(slotFileJobClose(KIO::Job*))); + m_result = -1; + + enterLoop(); + QEXPECT_FAIL("", "Needs fixing in kio_file", Abort); + QVERIFY(m_result == 0); // no error + + KIO::StoredTransferJob *getJob = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); + getJob->setUiDelegate(0); + connect(getJob, SIGNAL(result(KJob*)), + this, SLOT(slotGetResult(KJob*))); + enterLoop(); + QCOMPARE(m_result, 0); // no error + qDebug() << "m_data: " << m_data; + QCOMPARE(m_data, QByteArray("test....test....test....test....test....test....end")); + +} + +void JobRemoteTest::slotFileJobData(KIO::Job *job, const QByteArray &data) +{ + Q_UNUSED(job); + Q_UNUSED(data); +} + +void JobRemoteTest::slotFileJobRedirection(KIO::Job *job, const QUrl &url) +{ + Q_UNUSED(job); + Q_UNUSED(url); +} + +void JobRemoteTest::slotFileJobMimetype(KIO::Job *job, const QString &type) +{ + Q_UNUSED(job); + Q_UNUSED(type); +} + +void JobRemoteTest::slotFileJobOpen(KIO::Job *job) +{ + Q_UNUSED(job); + fileJob->seek(0); +} + +void JobRemoteTest::slotFileJobWritten(KIO::Job *job, KIO::filesize_t written) +{ + Q_UNUSED(job); + Q_UNUSED(written); + if (m_rwCount > 5) { + fileJob->close(); + } else { + fileJob->seek(m_rwCount * 8); + m_rwCount++; + } +} + +void JobRemoteTest::slotFileJobPosition(KIO::Job *job, KIO::filesize_t offset) +{ + Q_UNUSED(job); + Q_UNUSED(offset); + const QByteArray data("test....end"); + fileJob->write(data); + +} + +void JobRemoteTest::slotFileJobClose(KIO::Job *job) +{ + Q_UNUSED(job); + qDebug() << "+++++++++ closed"; +} + +//// + +void JobRemoteTest::openFileReading() +{ + QUrl u(remoteTmpUrl()); + u.setPath(u.path() + "openFileReading"); + + const QByteArray putData("test1test2test3test4test5"); + + KIO::StoredTransferJob *putJob = KIO::storedPut(putData, + u, + 0600, KIO::Overwrite | KIO::HideProgressInfo + ); + + QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds + putJob->setModificationTime(mtime); + putJob->setUiDelegate(0); + connect(putJob, SIGNAL(result(KJob*)), + this, SLOT(slotResult(KJob*))); + m_result = -1; + enterLoop(); + QVERIFY(m_result == 0); // no error + + m_rwCount = 4; + m_data = QByteArray(); + + fileJob = KIO::open(u, QIODevice::ReadOnly); + + fileJob->setUiDelegate(0); + connect(fileJob, SIGNAL(result(KJob*)), + this, SLOT(slotResult(KJob*))); + connect(fileJob, SIGNAL(data(KIO::Job*,QByteArray)), + this, SLOT(slotFileJob2Data(KIO::Job*,QByteArray))); + connect(fileJob, SIGNAL(open(KIO::Job*)), + this, SLOT(slotFileJob2Open(KIO::Job*))); + connect(fileJob, SIGNAL(written(KIO::Job*,KIO::filesize_t)), + this, SLOT(slotFileJob2Written(KIO::Job*,KIO::filesize_t))); + connect(fileJob, SIGNAL(position(KIO::Job*,KIO::filesize_t)), + this, SLOT(slotFileJob2Position(KIO::Job*,KIO::filesize_t))); + connect(fileJob, SIGNAL(close(KIO::Job*)), + this, SLOT(slotFileJob2Close(KIO::Job*))); + m_result = -1; + + enterLoop(); + QVERIFY(m_result == 0); // no error + qDebug() << "resulting m_data: " << QString(m_data); + QCOMPARE(m_data, QByteArray("test5test4test3test2test1")); + +} + +void JobRemoteTest::slotFileJob2Data(KIO::Job *job, const QByteArray &data) +{ + Q_UNUSED(job); + qDebug() << "m_rwCount = " << m_rwCount << " data: " << data; + m_data.append(data); + + if (m_rwCount < 0) { + fileJob->close(); + } else { + fileJob->seek(m_rwCount-- * 5); + } +} + +void JobRemoteTest::slotFileJob2Redirection(KIO::Job *job, const QUrl &url) +{ + Q_UNUSED(job); + Q_UNUSED(url); +} + +void JobRemoteTest::slotFileJob2Mimetype(KIO::Job *job, const QString &type) +{ + Q_UNUSED(job); + qDebug() << "mimetype: " << type; +} + +void JobRemoteTest::slotFileJob2Open(KIO::Job *job) +{ + Q_UNUSED(job); + fileJob->seek(m_rwCount-- * 5); +} + +void JobRemoteTest::slotFileJob2Written(KIO::Job *job, KIO::filesize_t written) +{ + Q_UNUSED(job); + Q_UNUSED(written); +} + +void JobRemoteTest::slotFileJob2Position(KIO::Job *job, KIO::filesize_t offset) +{ + Q_UNUSED(job); + qDebug() << "position : " << offset << " -> read (5)"; + fileJob->read(5); +} + +void JobRemoteTest::slotFileJob2Close(KIO::Job *job) +{ + Q_UNUSED(job); + qDebug() << "+++++++++ job2 closed"; +} + +//// + +void JobRemoteTest::slotMimetype(KIO::Job *job, const QString &type) +{ + QVERIFY(job != 0); + m_mimetype = type; +} + diff --git a/autotests/jobremotetest.h b/autotests/jobremotetest.h new file mode 100644 index 0000000..e3374dd --- /dev/null +++ b/autotests/jobremotetest.h @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure + Copyright (C) 2008 Norbert Frese + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/* + Please set KIO_JOBREMOTETEST_REMOTETMP to test other protocols than kio_file. + Don't forget the trailing slash! +*/ + +#ifndef JOBTEST_H +#define JOBTEST_H + +#include +#include +#include + +class JobRemoteTest : public QObject +{ + Q_OBJECT + +public: + JobRemoteTest() {} + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + // Remote tests + void putAndGet(); + void openFileWriting(); + void openFileReading(); + + //void calculateRemainingSeconds(); + +Q_SIGNALS: + void exitLoop(); + +protected Q_SLOTS: + //void slotEntries( KIO::Job*, const KIO::UDSEntryList& lst ); + void slotGetResult(KJob *); + void slotDataReq(KIO::Job *, QByteArray &); + void slotResult(KJob *); + void slotMimetype(KIO::Job *, const QString &); + + void slotFileJobData(KIO::Job *job, const QByteArray &data); + void slotFileJobRedirection(KIO::Job *job, const QUrl &url); + void slotFileJobMimetype(KIO::Job *job, const QString &type); + void slotFileJobOpen(KIO::Job *job); + void slotFileJobWritten(KIO::Job *job, KIO::filesize_t written); + void slotFileJobPosition(KIO::Job *job, KIO::filesize_t offset); + void slotFileJobClose(KIO::Job *job); + + void slotFileJob2Data(KIO::Job *job, const QByteArray &data); + void slotFileJob2Redirection(KIO::Job *job, const QUrl &url); + void slotFileJob2Mimetype(KIO::Job *job, const QString &type); + void slotFileJob2Open(KIO::Job *job); + void slotFileJob2Written(KIO::Job *job, KIO::filesize_t written); + void slotFileJob2Position(KIO::Job *job, KIO::filesize_t offset); + void slotFileJob2Close(KIO::Job *job); + +private: + void enterLoop(); + enum { AlreadyExists = 1 }; + + int m_result; + QByteArray m_data; + QStringList m_names; + int m_dataReqCount; + QString m_mimetype; + + // openReadWrite test + KIO::FileJob *fileJob; + int m_rwCount; +}; + +#endif diff --git a/autotests/jobtest.cpp b/autotests/jobtest.cpp new file mode 100644 index 0000000..ac0dfa8 --- /dev/null +++ b/autotests/jobtest.cpp @@ -0,0 +1,1751 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2006 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include "jobtest.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "kiotesthelper.h" // createTestFile etc. +#ifndef Q_OS_WIN +#include // for readlink +#endif + +QTEST_MAIN(JobTest) + +// The code comes partly from kdebase/kioslave/trash/testtrash.cpp + +static QString otherTmpDir() +{ +#ifdef Q_OS_WIN + return QDir::tempPath() + "/jobtest/"; +#else + // This one needs to be on another partition + return QStringLiteral("/tmp/jobtest/"); +#endif +} + +#if 0 +static QUrl systemTmpDir() +{ +#ifdef Q_OS_WIN + return QUrl("system:" + QDir::homePath() + "/.kde-unit-test/jobtest-system/"); +#else + return QUrl("system:/home/.kde-unit-test/jobtest-system/"); +#endif +} + +static QString realSystemPath() +{ + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/jobtest-system/"; +} +#endif + +void JobTest::initTestCase() +{ + QStandardPaths::enableTestMode(true); + QCoreApplication::instance()->setApplicationName("kio/jobtest"); // testing for #357499 + + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + + s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + + // Start with a clean base dir + cleanupTestCase(); + homeTmpDir(); // create it + if (!QFile::exists(otherTmpDir())) { + bool ok = QDir().mkdir(otherTmpDir()); + if (!ok) { + qFatal("couldn't create %s", qPrintable(otherTmpDir())); + } + } +#if 0 + if (KProtocolInfo::isKnownProtocol("system")) { + if (!QFile::exists(realSystemPath())) { + bool ok = dir.mkdir(realSystemPath()); + if (!ok) { + qFatal("couldn't create %s", qPrintable(realSystemPath())); + } + } + } +#endif + + qRegisterMetaType("KJob*"); + qRegisterMetaType("KIO::Job*"); + qRegisterMetaType("QDateTime"); +} + +void JobTest::cleanupTestCase() +{ + QDir(homeTmpDir()).removeRecursively(); + QDir(otherTmpDir()).removeRecursively(); +#if 0 + if (KProtocolInfo::isKnownProtocol("system")) { + delDir(systemTmpDir()); + } +#endif +} + +void JobTest::enterLoop() +{ + QEventLoop eventLoop; + connect(this, SIGNAL(exitLoop()), + &eventLoop, SLOT(quit())); + eventLoop.exec(QEventLoop::ExcludeUserInputEvents); +} + +void JobTest::storedGet() +{ + qDebug(); + const QString filePath = homeTmpDir() + "fileFromHome"; + createTestFile(filePath); + QUrl u = QUrl::fromLocalFile(filePath); + m_result = -1; + + KIO::StoredTransferJob *job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); + QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); + QVERIFY(spyPercent.isValid()); + job->setUiDelegate(0); + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotGetResult(KJob*))); + enterLoop(); + QCOMPARE(m_result, 0); // no error + QCOMPARE(m_data, QByteArray("Hello\0world", 11)); + QCOMPARE(m_data.size(), 11); + QVERIFY(!spyPercent.isEmpty()); +} + +void JobTest::slotGetResult(KJob *job) +{ + m_result = job->error(); + m_data = static_cast(job)->data(); + emit exitLoop(); +} + +void JobTest::put() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + QUrl u = QUrl::fromLocalFile(filePath); + KIO::TransferJob *job = KIO::put(u, 0600, KIO::Overwrite | KIO::HideProgressInfo); + QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds + job->setModificationTime(mtime); + job->setUiDelegate(0); + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotResult(KJob*))); + connect(job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), + this, SLOT(slotDataReq(KIO::Job*,QByteArray&))); + m_result = -1; + m_dataReqCount = 0; + enterLoop(); + QVERIFY(m_result == 0); // no error + + QFileInfo fileInfo(filePath); + QVERIFY(fileInfo.exists()); + QCOMPARE(fileInfo.size(), 30LL); // "This is a test for KIO::put()\n" + QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); + QCOMPARE(fileInfo.lastModified(), mtime); +} + +void JobTest::slotDataReq(KIO::Job *, QByteArray &data) +{ + // Really not the way you'd write a slotDataReq usually :) + switch (m_dataReqCount++) { + case 0: + data = "This is a test for "; + break; + case 1: + data = "KIO::put()\n"; + break; + case 2: + data = QByteArray(); + break; + } +} + +void JobTest::slotResult(KJob *job) +{ + m_result = job->error(); + emit exitLoop(); +} + +void JobTest::storedPut() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + QUrl u = QUrl::fromLocalFile(filePath); + QByteArray putData = "This is the put data"; + KIO::TransferJob *job = KIO::storedPut(putData, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); + QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); + QVERIFY(spyPercent.isValid()); + QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds + job->setModificationTime(mtime); + job->setUiDelegate(0); + QVERIFY(job->exec()); + QFileInfo fileInfo(filePath); + QVERIFY(fileInfo.exists()); + QCOMPARE(fileInfo.size(), (long long)putData.size()); + QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); + QCOMPARE(fileInfo.lastModified(), mtime); + QVERIFY(!spyPercent.isEmpty()); +} + +void JobTest::storedPutIODevice() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + QBuffer putData; + putData.setData("This is the put data"); + QVERIFY(putData.open(QIODevice::ReadOnly)); + KIO::TransferJob *job = KIO::storedPut(&putData, QUrl::fromLocalFile(filePath), 0600, KIO::Overwrite | KIO::HideProgressInfo); + QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); + QVERIFY(spyPercent.isValid()); + QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds + job->setModificationTime(mtime); + job->setUiDelegate(0); + QVERIFY(job->exec()); + QFileInfo fileInfo(filePath); + QVERIFY(fileInfo.exists()); + QCOMPARE(fileInfo.size(), (long long)putData.size()); + QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); + QCOMPARE(fileInfo.lastModified(), mtime); + QVERIFY(!spyPercent.isEmpty()); +} + +void JobTest::storedPutIODeviceFile() +{ + // Given a source file and a destination file + const QString src = homeTmpDir() + "fileFromHome"; + createTestFile(src); + QVERIFY(QFile::exists(src)); + QFile srcFile(src); + QVERIFY(srcFile.open(QIODevice::ReadOnly)); + const QString dest = homeTmpDir() + "fileFromHome_copied"; + QFile::remove(dest); + const QUrl destUrl = QUrl::fromLocalFile(dest); + + // When using storedPut with the QFile as argument + KIO::StoredTransferJob *job = KIO::storedPut(&srcFile, destUrl, 0600, KIO::Overwrite | KIO::HideProgressInfo); + + // Then the copy should succeed and the dest file exist + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QVERIFY(QFile::exists(dest)); + QCOMPARE(QFileInfo(src).size(), QFileInfo(dest).size()); + QFile::remove(dest); +} + +void JobTest::storedPutIODeviceTempFile() +{ + // Create a temp file in the current dir. + QTemporaryFile tempFile(QStringLiteral("jobtest-tmp")); + QVERIFY(tempFile.open()); + + // Write something into the file. + QTextStream stream(&tempFile); + stream << QStringLiteral("This is the put data"); + stream.flush(); + QVERIFY(QFileInfo(tempFile).size() > 0); + + const QString dest = homeTmpDir() + QLatin1String("tmpfile-dest"); + const QUrl destUrl = QUrl::fromLocalFile(dest); + + // QTemporaryFiles are open in ReadWrite mode, + // so we don't need to close and reopen, + // but we need to rewind to the beginning. + tempFile.seek(0); + auto job = KIO::storedPut(&tempFile, destUrl, -1); + + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QVERIFY(QFileInfo::exists(dest)); + QCOMPARE(QFileInfo(dest).size(), QFileInfo(tempFile).size()); + QVERIFY(QFile::remove(dest)); +} + +void JobTest::storedPutIODeviceSlowDevice() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + const QUrl u = QUrl::fromLocalFile(filePath); + const QByteArray putDataContents = "This is the put data"; + QBuffer putDataBuffer; + QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); + + KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); + QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong))); + QVERIFY(spyPercent.isValid()); + QDateTime mtime = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds + job->setModificationTime(mtime); + job->setTotalSize(putDataContents.size()); + job->setUiDelegate(0); + job->setAsyncDataEnabled(true); + + QTimer t; + t.setSingleShot(true); + int size = -1; + connect(&t, &QTimer::timeout, this, [&putDataBuffer, &size, putDataContents]() { + putDataBuffer.write(putDataContents); + putDataBuffer.seek(0); +// qDebug() << "written" << putDataBuffer.size(); + size = putDataBuffer.bytesAvailable(); + }); + t.start(200); + QVERIFY(job->exec()); + QCOMPARE(size, putDataContents.size()); + QCOMPARE(putDataBuffer.bytesAvailable(), 0); + + QFileInfo fileInfo(filePath); + QVERIFY(fileInfo.exists()); + QCOMPARE(fileInfo.size(), (long long)putDataContents.size()); + QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); + QCOMPARE(fileInfo.lastModified(), mtime); + QVERIFY(!spyPercent.isEmpty()); +} + +//// + +void JobTest::copyLocalFile(const QString &src, const QString &dest) +{ + const QUrl u = QUrl::fromLocalFile(src); + const QUrl d = QUrl::fromLocalFile(dest); + + const int perms = 0666; + // copy the file with file_copy + KIO::Job *job = KIO::file_copy(u, d, perms, KIO::HideProgressInfo); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QFile::exists(dest)); + QVERIFY(QFile::exists(src)); // still there + QCOMPARE(int(QFileInfo(dest).permissions()), int(0x6666)); + + { + // check that the timestamp is the same (#24443) + // Note: this only works because of copy() in kio_file. + // The datapump solution ignores mtime, the app has to call FileCopyJob::setModificationTime() + QFileInfo srcInfo(src); + QFileInfo destInfo(dest); +#ifdef Q_OS_WIN + // win32 time may differs in msec part + QCOMPARE(srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"), + destInfo.lastModified().toString("dd.MM.yyyy hh:mm")); +#else + QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); +#endif + } + + // cleanup and retry with KIO::copy() + QFile::remove(dest); + job = KIO::copy(u, d, KIO::HideProgressInfo); + QSignalSpy spyCopyingDone(job, SIGNAL(copyingDone(KIO::Job*,QUrl,QUrl,QDateTime,bool,bool))); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + ok = job->exec(); + QVERIFY(ok); + QVERIFY(QFile::exists(dest)); + QVERIFY(QFile::exists(src)); // still there + { + // check that the timestamp is the same (#24443) + QFileInfo srcInfo(src); + QFileInfo destInfo(dest); +#ifdef Q_OS_WIN + // win32 time may differs in msec part + QCOMPARE(srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"), + destInfo.lastModified().toString("dd.MM.yyyy hh:mm")); +#else + QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); +#endif + } + QCOMPARE(spyCopyingDone.count(), 1); + + // cleanup and retry with KIO::copyAs() + QFile::remove(dest); + job = KIO::copyAs(u, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + QVERIFY(job->exec()); + QVERIFY(QFile::exists(dest)); + QVERIFY(QFile::exists(src)); // still there + + // Do it again, with Overwrite. + job = KIO::copyAs(u, d, KIO::Overwrite | KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + QVERIFY(job->exec()); + QVERIFY(QFile::exists(dest)); + QVERIFY(QFile::exists(src)); // still there + + // Do it again, without Overwrite (should fail). + job = KIO::copyAs(u, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + QVERIFY(!job->exec()); +} + +void JobTest::copyLocalDirectory(const QString &src, const QString &_dest, int flags) +{ + QVERIFY(QFileInfo(src).isDir()); + QVERIFY(QFileInfo(src + "/testfile").isFile()); + QUrl u = QUrl::fromLocalFile(src); + QString dest(_dest); + QUrl d = QUrl::fromLocalFile(dest); + if (flags & AlreadyExists) { + QVERIFY(QFile::exists(dest)); + } else { + QVERIFY(!QFile::exists(dest)); + } + + KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QFile::exists(dest)); + QVERIFY(QFileInfo(dest).isDir()); + QVERIFY(QFileInfo(dest + "/testfile").isFile()); + QVERIFY(QFile::exists(src)); // still there + + if (flags & AlreadyExists) { + dest += '/' + u.fileName(); + //qDebug() << "Expecting dest=" << dest; + } + + // CopyJob::setNextDirAttribute isn't implemented for Windows currently. +#ifndef Q_OS_WIN + { + // Check that the timestamp is the same (#24443) + QFileInfo srcInfo(src); + QFileInfo destInfo(dest); + QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); + } +#endif + + // Do it again, with Overwrite. + // Use copyAs, we don't want a subdir inside d. + job = KIO::copyAs(u, d, KIO::HideProgressInfo | KIO::Overwrite); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + ok = job->exec(); + QVERIFY(ok); + + // Do it again, without Overwrite (should fail). + job = KIO::copyAs(u, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + ok = job->exec(); + QVERIFY(!ok); +} + +#ifndef Q_OS_WIN +static QString linkTarget(const QString &path) +{ + // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927) + char linkTargetBuffer[4096]; + const int n = readlink(QFile::encodeName(path).constData(), linkTargetBuffer, sizeof(linkTargetBuffer) - 1); + if (n != -1) { + linkTargetBuffer[n] = 0; + } + return QFile::decodeName(linkTargetBuffer); +} + +static void copyLocalSymlink(const QString &src, const QString &dest, const QString &expectedLinkTarget) +{ + QT_STATBUF buf; + QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); + QUrl u = QUrl::fromLocalFile(src); + QUrl d = QUrl::fromLocalFile(dest); + + // copy the symlink + KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + QVERIFY2(job->exec(), qPrintable(job->error())); + QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0); // dest exists + QCOMPARE(linkTarget(dest), expectedLinkTarget); + + // cleanup + QFile::remove(dest); +} +#endif + +void JobTest::copyFileToSamePartition() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + const QString dest = homeTmpDir() + "fileFromHome_copied"; + createTestFile(filePath); + copyLocalFile(filePath, dest); +} + +void JobTest::copyDirectoryToSamePartition() +{ + qDebug(); + const QString src = homeTmpDir() + "dirFromHome"; + const QString dest = homeTmpDir() + "dirFromHome_copied"; + createTestDirectory(src); + copyLocalDirectory(src, dest); +} + +void JobTest::copyDirectoryToExistingDirectory() +{ + qDebug(); + // just the same as copyDirectoryToSamePartition, but this time dest exists. + // So we get a subdir, "dirFromHome_copy/dirFromHome" + const QString src = homeTmpDir() + "dirFromHome"; + const QString dest = homeTmpDir() + "dirFromHome_copied"; + createTestDirectory(src); + createTestDirectory(dest); + copyLocalDirectory(src, dest, AlreadyExists); +} + +void JobTest::copyFileToOtherPartition() +{ + qDebug(); + const QString filePath = homeTmpDir() + "fileFromHome"; + const QString dest = otherTmpDir() + "fileFromHome_copied"; + createTestFile(filePath); + copyLocalFile(filePath, dest); +} + +void JobTest::copyDirectoryToOtherPartition() +{ + qDebug(); + const QString src = homeTmpDir() + "dirFromHome"; + const QString dest = otherTmpDir() + "dirFromHome_copied"; + createTestDirectory(src); + copyLocalDirectory(src, dest); +} + +void JobTest::copyRelativeSymlinkToSamePartition() // #352927 +{ +#ifdef Q_OS_WIN + QSKIP("Skipping symlink test on Windows"); +#else + const QString filePath = homeTmpDir() + "testlink"; + const QString dest = homeTmpDir() + "testlink_copied"; + createTestSymlink(filePath, "relative"); + copyLocalSymlink(filePath, dest, QStringLiteral("relative")); + QFile::remove(filePath); +#endif +} + +void JobTest::copyAbsoluteSymlinkToOtherPartition() +{ +#ifdef Q_OS_WIN + QSKIP("Skipping symlink test on Windows"); +#else + const QString filePath = homeTmpDir() + "testlink"; + const QString dest = otherTmpDir() + "testlink_copied"; + createTestSymlink(filePath, QFile::encodeName(homeTmpDir())); + copyLocalSymlink(filePath, dest, homeTmpDir()); + QFile::remove(filePath); +#endif +} + +void JobTest::copyFolderWithUnaccessibleSubfolder() +{ + const QString src_dir = homeTmpDir() + "srcHome"; + const QString dst_dir = homeTmpDir() + "dstHome"; + + QDir().remove(src_dir); + QDir().remove(dst_dir); + + createTestDirectory(src_dir); + createTestDirectory(src_dir + "/folder1"); + QString inaccessible = src_dir + "/folder1/inaccessible"; + createTestDirectory(inaccessible); + + QFile(inaccessible).setPermissions(QFile::Permissions()); // Make it inaccessible + //Copying should throw some warnings, as it cannot access some folders + + KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(src_dir), QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo); + + QSignalSpy spy(job, SIGNAL(warning(KJob*,QString,QString))); + job->setUiDelegate(0); // no skip dialog, thanks + QVERIFY(job->exec()); + + QFile(inaccessible).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)); + + KIO::DeleteJob *deljob1 = KIO::del(QUrl::fromLocalFile(src_dir), KIO::HideProgressInfo); + deljob1->setUiDelegate(0); // no skip dialog, thanks + QVERIFY(deljob1->exec()); + + KIO::DeleteJob *deljob2 = KIO::del(QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo); + deljob2->setUiDelegate(0); // no skip dialog, thanks + QVERIFY(deljob2->exec()); + + QCOMPARE(spy.count(), 1); // one warning should be emitted by the copy job + +} + +void JobTest::moveLocalFile(const QString &src, const QString &dest) +{ + QVERIFY(QFile::exists(src)); + QUrl u = QUrl::fromLocalFile(src); + QUrl d = QUrl::fromLocalFile(dest); + + // move the file with file_move + KIO::Job *job = KIO::file_move(u, d, 0666, KIO::HideProgressInfo); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QFile::exists(dest)); + QVERIFY(!QFile::exists(src)); // not there anymore + QCOMPARE(int(QFileInfo(dest).permissions()), int(0x6666)); + + // move it back with KIO::move() + job = KIO::move(d, u, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(dest)); + QVERIFY(QFile::exists(src)); // it's back +} + +static void moveLocalSymlink(const QString &src, const QString &dest) +{ + QT_STATBUF buf; + QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); + QUrl u = QUrl::fromLocalFile(src); + QUrl d = QUrl::fromLocalFile(dest); + + // move the symlink with move, NOT with file_move + KIO::Job *job = KIO::move(u, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + bool ok = job->exec(); + if (!ok) { + qWarning() << job->error(); + } + QVERIFY(ok); + QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0); + QVERIFY(!QFile::exists(src)); // not there anymore + + // move it back with KIO::move() + job = KIO::move(d, u, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + ok = job->exec(); + QVERIFY(ok); + QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) != 0); // doesn't exist anymore + QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); // it's back +} + +void JobTest::moveLocalDirectory(const QString &src, const QString &dest) +{ + qDebug() << src << " " << dest; + QVERIFY(QFile::exists(src)); + QVERIFY(QFileInfo(src).isDir()); + QVERIFY(QFileInfo(src + "/testfile").isFile()); +#ifndef Q_OS_WIN + QVERIFY(QFileInfo(src + "/testlink").isSymLink()); +#endif + QUrl u = QUrl::fromLocalFile(src); + QUrl d = QUrl::fromLocalFile(dest); + + KIO::Job *job = KIO::move(u, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + bool ok = job->exec(); + QVERIFY2(ok, qPrintable(job->errorString())); + QVERIFY(QFile::exists(dest)); + QVERIFY(QFileInfo(dest).isDir()); + QVERIFY(QFileInfo(dest + "/testfile").isFile()); + QVERIFY(!QFile::exists(src)); // not there anymore +#ifndef Q_OS_WIN + QVERIFY(QFileInfo(dest + "/testlink").isSymLink()); +#endif +} + +void JobTest::moveFileToSamePartition() +{ + qDebug(); + const QString filePath = homeTmpDir() + "fileFromHome"; + const QString dest = homeTmpDir() + "fileFromHome_moved"; + createTestFile(filePath); + moveLocalFile(filePath, dest); +} + +void JobTest::moveDirectoryToSamePartition() +{ + qDebug(); + const QString src = homeTmpDir() + "dirFromHome"; + const QString dest = homeTmpDir() + "dirFromHome_moved"; + createTestDirectory(src); + moveLocalDirectory(src, dest); +} + +void JobTest::moveDirectoryIntoItself() +{ + qDebug(); + const QString src = homeTmpDir() + "dirFromHome"; + const QString dest = src + "/foo"; + createTestDirectory(src); + QVERIFY(QFile::exists(src)); + QUrl u = QUrl::fromLocalFile(src); + QUrl d = QUrl::fromLocalFile(dest); + KIO::CopyJob *job = KIO::move(u, d); + QVERIFY(!job->exec()); + QCOMPARE(job->error(), (int)KIO::ERR_CANNOT_MOVE_INTO_ITSELF); + QCOMPARE(job->errorString(), i18n("A folder cannot be moved into itself")); + QDir(dest).removeRecursively(); +} + +void JobTest::moveFileToOtherPartition() +{ + qDebug(); + const QString filePath = homeTmpDir() + "fileFromHome"; + const QString dest = otherTmpDir() + "fileFromHome_moved"; + createTestFile(filePath); + moveLocalFile(filePath, dest); +} + +void JobTest::moveSymlinkToOtherPartition() +{ +#ifndef Q_OS_WIN + qDebug(); + const QString filePath = homeTmpDir() + "testlink"; + const QString dest = otherTmpDir() + "testlink_moved"; + createTestSymlink(filePath); + moveLocalSymlink(filePath, dest); +#endif +} + +void JobTest::moveDirectoryToOtherPartition() +{ + qDebug(); +#ifndef Q_OS_WIN + const QString src = homeTmpDir() + "dirFromHome"; + const QString dest = otherTmpDir() + "dirFromHome_moved"; + createTestDirectory(src); + moveLocalDirectory(src, dest); +#endif +} + +void JobTest::moveFileNoPermissions() +{ + // Given a file that cannot be moved (subdir has no permissions) + const QString subdir = homeTmpDir() + "subdir"; + QVERIFY(QDir().mkpath(subdir)); + const QString src = subdir + "/thefile"; + createTestFile(src); + QVERIFY(QFile(subdir).setPermissions(QFile::Permissions())); // Make it inaccessible + + // When trying to move it + const QString dest = homeTmpDir() + "dest"; + KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); // no skip dialog, thanks + + // The job should fail with "access denied" + QVERIFY(!job->exec()); + QCOMPARE(job->error(), (int)KIO::ERR_ACCESS_DENIED); + // Note that, just like mv(1), KIO's behavior depends on whether + // a direct rename(2) was used, or a full copy+del. In the first case + // there is no destination file created, but in the second case the + // destination file remains. + // In this test it's the same partition, so no dest created. + QVERIFY(!QFile::exists(dest)); + + // Cleanup + QVERIFY(QFile(subdir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner))); + QVERIFY(QFile::exists(src)); + QVERIFY(QDir(subdir).removeRecursively()); +} + +void JobTest::moveDirectoryNoPermissions() +{ + // Given a dir that cannot be moved (parent dir has no permissions) + const QString subdir = homeTmpDir() + "subdir"; + const QString src = subdir + "/thedir"; + QVERIFY(QDir().mkpath(src)); + QVERIFY(QFileInfo(src).isDir()); + QVERIFY(QFile(subdir).setPermissions(QFile::Permissions())); // Make it inaccessible + + // When trying to move it + const QString dest = homeTmpDir() + "mdnp"; + KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); // no skip dialog, thanks + + // The job should fail with "access denied" + QVERIFY(!job->exec()); + QCOMPARE(job->error(), (int)KIO::ERR_ACCESS_DENIED); + + QVERIFY(!QFile::exists(dest)); + + // Cleanup + QVERIFY(QFile(subdir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner))); + QVERIFY(QFile::exists(src)); + QVERIFY(QDir(subdir).removeRecursively()); +} + +void JobTest::listRecursive() +{ + // Note: many other tests must have been run before since we rely on the files they created + + const QString src = homeTmpDir(); +#ifndef Q_OS_WIN + // Add a symlink to a dir, to make sure we don't recurse into those + bool symlinkOk = symlink("dirFromHome", QFile::encodeName(src + "/dirFromHome_link").constData()) == 0; + QVERIFY(symlinkOk); +#endif + KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src), KIO::HideProgressInfo); + job->setUiDelegate(0); + connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), + SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); + bool ok = job->exec(); + QVERIFY(ok); + m_names.sort(); + QByteArray ref_names = QByteArray(".,..," + "dirFromHome,dirFromHome/testfile," +#ifndef Q_OS_WIN + "dirFromHome/testlink," +#endif + "dirFromHome_copied," + "dirFromHome_copied/dirFromHome,dirFromHome_copied/dirFromHome/testfile," +#ifndef Q_OS_WIN + "dirFromHome_copied/dirFromHome/testlink," +#endif + "dirFromHome_copied/testfile," +#ifndef Q_OS_WIN + "dirFromHome_copied/testlink,dirFromHome_link," +#endif + "fileFromHome,fileFromHome_copied"); + + const QString joinedNames = m_names.join(QStringLiteral(",")); + if (joinedNames.toLatin1() != ref_names) { + qDebug("%s", qPrintable(joinedNames)); + qDebug("%s", ref_names.data()); + } + QCOMPARE(joinedNames.toLatin1(), ref_names); +} + +void JobTest::listFile() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + createTestFile(filePath); + KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); + job->setUiDelegate(0); + QVERIFY(!job->exec()); + QCOMPARE(job->error(), static_cast(KIO::ERR_IS_FILE)); + + // And list something that doesn't exist + const QString path = homeTmpDir() + "fileFromHomeDoesNotExist"; + job = KIO::listDir(QUrl::fromLocalFile(path), KIO::HideProgressInfo); + job->setUiDelegate(0); + QVERIFY(!job->exec()); + QCOMPARE(job->error(), static_cast(KIO::ERR_DOES_NOT_EXIST)); +} + +void JobTest::killJob() +{ + const QString src = homeTmpDir(); + KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(src), KIO::HideProgressInfo); + QVERIFY(job->isAutoDelete()); + QPointer ptr(job); + job->setUiDelegate(0); + qApp->processEvents(); // let the job start, it's no fun otherwise + job->kill(); + qApp->sendPostedEvents(0, QEvent::DeferredDelete); // process the deferred delete of the job + QVERIFY(ptr.isNull()); +} + +void JobTest::killJobBeforeStart() +{ + const QString src = homeTmpDir(); + KIO::Job *job = KIO::stat(QUrl::fromLocalFile(src), KIO::HideProgressInfo); + QVERIFY(job->isAutoDelete()); + QPointer ptr(job); + job->setUiDelegate(0); + job->kill(); + qApp->sendPostedEvents(0, QEvent::DeferredDelete); // process the deferred delete of the job + QVERIFY(ptr.isNull()); + qApp->processEvents(); // does KIO scheduler crash here? nope. +} + +void JobTest::deleteJobBeforeStart() // #163171 +{ + const QString src = homeTmpDir(); + KIO::Job *job = KIO::stat(QUrl::fromLocalFile(src), KIO::HideProgressInfo); + QVERIFY(job->isAutoDelete()); + job->setUiDelegate(0); + delete job; + qApp->processEvents(); // does KIO scheduler crash here? +} + +void JobTest::directorySize() +{ + // Note: many other tests must have been run before since we rely on the files they created + + const QString src = homeTmpDir(); + + KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(src)); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + qDebug() << "totalSize: " << job->totalSize(); + qDebug() << "totalFiles: " << job->totalFiles(); + qDebug() << "totalSubdirs: " << job->totalSubdirs(); +#ifdef Q_OS_WIN + QCOMPARE(job->totalFiles(), 5ULL); // see expected result in listRecursive() above + QCOMPARE(job->totalSubdirs(), 3ULL); // see expected result in listRecursive() above + QVERIFY(job->totalSize() > 54); +#else + QCOMPARE(job->totalFiles(), 8ULL); // see expected result in listRecursive() above + QCOMPARE(job->totalSubdirs(), 4ULL); // see expected result in listRecursive() above + QVERIFY(job->totalSize() >= 325); +#endif + + qApp->sendPostedEvents(0, QEvent::DeferredDelete); +} + +void JobTest::directorySizeError() +{ + KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(QStringLiteral("/I/Dont/Exist"))); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(!ok); + qApp->sendPostedEvents(0, QEvent::DeferredDelete); +} + +void JobTest::slotEntries(KIO::Job *, const KIO::UDSEntryList &lst) +{ + for (KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it) { + QString displayName = (*it).stringValue(KIO::UDSEntry::UDS_NAME); + //QUrl url = (*it).stringValue( KIO::UDSEntry::UDS_URL ); + m_names.append(displayName); + } +} + +void JobTest::calculateRemainingSeconds() +{ + unsigned int seconds = KIO::calculateRemainingSeconds(2 * 86400 - 60, 0, 1); + QCOMPARE(seconds, static_cast(2 * 86400 - 60)); + QString text = KIO::convertSeconds(seconds); + QCOMPARE(text, i18n("1 day 23:59:00")); + + seconds = KIO::calculateRemainingSeconds(520, 20, 10); + QCOMPARE(seconds, static_cast(50)); + text = KIO::convertSeconds(seconds); + QCOMPARE(text, i18n("00:00:50")); +} + +#if 0 +void JobTest::copyFileToSystem() +{ + if (!KProtocolInfo::isKnownProtocol("system")) { + qDebug() << "no kio_system, skipping test"; + return; + } + + // First test with support for UDS_LOCAL_PATH + copyFileToSystem(true); + + QString dest = realSystemPath() + "fileFromHome_copied"; + QFile::remove(dest); + + // Then disable support for UDS_LOCAL_PATH, i.e. test what would + // happen for ftp, smb, http etc. + copyFileToSystem(false); +} + +void JobTest::copyFileToSystem(bool resolve_local_urls) +{ + qDebug() << resolve_local_urls; + extern KIOCORE_EXPORT bool kio_resolve_local_urls; + kio_resolve_local_urls = resolve_local_urls; + + const QString src = homeTmpDir() + "fileFromHome"; + createTestFile(src); + QUrl u = QUrl::fromLocalFile(src); + QUrl d = QUrl::fromLocalFile(systemTmpDir()); + d.addPath("fileFromHome_copied"); + + qDebug() << "copying " << u << " to " << d; + + // copy the file with file_copy + m_mimetype.clear(); + KIO::FileCopyJob *job = KIO::file_copy(u, d, -1, KIO::HideProgressInfo); + job->setUiDelegate(0); + connect(job, SIGNAL(mimetype(KIO::Job*,QString)), + this, SLOT(slotMimetype(KIO::Job*,QString))); + bool ok = job->exec(); + QVERIFY(ok); + + QString dest = realSystemPath() + "fileFromHome_copied"; + + QVERIFY(QFile::exists(dest)); + QVERIFY(QFile::exists(src)); // still there + + { + // do NOT check that the timestamp is the same. + // It can't work with file_copy when it uses the datapump, + // unless we use setModificationTime in the app code. + } + + // Check mimetype + QCOMPARE(m_mimetype, QString("text/plain")); + + // cleanup and retry with KIO::copy() + QFile::remove(dest); + job = KIO::copy(u, d, KIO::HideProgressInfo); + job->setUiDelegate(0); + ok = job->exec(); + QVERIFY(ok); + QVERIFY(QFile::exists(dest)); + QVERIFY(QFile::exists(src)); // still there + { + // check that the timestamp is the same (#79937) + QFileInfo srcInfo(src); + QFileInfo destInfo(dest); + QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); + } + + // restore normal behavior + kio_resolve_local_urls = true; +} +#endif + +void JobTest::getInvalidUrl() +{ + QUrl url(QStringLiteral("http://strange/")); + QVERIFY(!url.isValid()); + + KIO::SimpleJob *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); + QVERIFY(job != 0); + job->setUiDelegate(0); + + KIO::Scheduler::setJobPriority(job, 1); // shouldn't crash (#135456) + + bool ok = job->exec(); + QVERIFY(!ok); // it should fail :) +} + +void JobTest::slotMimetype(KIO::Job *job, const QString &type) +{ + QVERIFY(job != 0); + m_mimetype = type; +} + +void JobTest::deleteFile() +{ + const QString dest = otherTmpDir() + "fileFromHome_copied"; + QVERIFY(QFile::exists(dest)); + KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(dest)); +} + +void JobTest::deleteDirectory() +{ + const QString dest = otherTmpDir() + "dirFromHome_copied"; + if (!QFile::exists(dest)) { + createTestDirectory(dest); + } + // Let's put a few things in there to see if the recursive deletion works correctly + // A hidden file: + createTestFile(dest + "/.hidden"); +#ifndef Q_OS_WIN + // A broken symlink: + createTestSymlink(dest + "/broken_symlink"); + // A symlink to a dir: + bool symlink_ok = symlink(QFile::encodeName(QFileInfo(QFINDTESTDATA("jobtest.cpp")).absolutePath()).constData(), + QFile::encodeName(dest + "/symlink_to_dir").constData()) == 0; + if (!symlink_ok) { + qFatal("couldn't create symlink: %s", strerror(errno)); + } +#endif + + KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(dest)); +} + +void JobTest::deleteSymlink(bool using_fast_path) +{ + extern KIOCORE_EXPORT bool kio_resolve_local_urls; + kio_resolve_local_urls = !using_fast_path; + +#ifndef Q_OS_WIN + const QString src = homeTmpDir() + "dirFromHome"; + createTestDirectory(src); + QVERIFY(QFile::exists(src)); + const QString dest = homeTmpDir() + "/dirFromHome_link"; + if (!QFile::exists(dest)) { + // Add a symlink to a dir, to make sure we don't recurse into those + bool symlinkOk = symlink(QFile::encodeName(src).constData(), QFile::encodeName(dest).constData()) == 0; + QVERIFY(symlinkOk); + QVERIFY(QFile::exists(dest)); + } + KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(dest)); + QVERIFY(QFile::exists(src)); +#endif + + kio_resolve_local_urls = true; +} + +void JobTest::deleteSymlink() +{ +#ifndef Q_OS_WIN + deleteSymlink(true); + deleteSymlink(false); +#endif +} + +void JobTest::deleteManyDirs(bool using_fast_path) +{ + extern KIOCORE_EXPORT bool kio_resolve_local_urls; + kio_resolve_local_urls = !using_fast_path; + + const int numDirs = 50; + QList dirs; + for (int i = 0; i < numDirs; ++i) { + const QString dir = homeTmpDir() + "dir" + QString::number(i); + createTestDirectory(dir); + dirs << QUrl::fromLocalFile(dir); + } + QTime dt; + dt.start(); + KIO::Job *job = KIO::del(dirs, KIO::HideProgressInfo); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + Q_FOREACH (const QUrl &dir, dirs) { + QVERIFY(!QFile::exists(dir.toLocalFile())); + } + + qDebug() << "Deleted" << numDirs << "dirs in" << dt.elapsed() << "milliseconds"; + kio_resolve_local_urls = true; +} + +void JobTest::deleteManyDirs() +{ + deleteManyDirs(true); + deleteManyDirs(false); +} + +static QList createManyFiles(const QString &baseDir, int numFiles) +{ + QList ret; + ret.reserve(numFiles); + for (int i = 0; i < numFiles; ++i) { + // create empty file + const QString file = baseDir + QString::number(i); + QFile f(file); + bool ok = f.open(QIODevice::WriteOnly); + if (ok) { + f.write("Hello"); + ret.append(QUrl::fromLocalFile(file)); + } + } + return ret; +} + +void JobTest::deleteManyFilesIndependently() +{ + QTime dt; + dt.start(); + const int numFiles = 100; // Use 1000 for performance testing + const QString baseDir = homeTmpDir(); + const QList urls = createManyFiles(baseDir, numFiles); + QCOMPARE(urls.count(), numFiles); + for (int i = 0; i < numFiles; ++i) { + // delete each file independently. lots of jobs. this stress-tests kio scheduling. + const QUrl url = urls.at(i); + const QString file = url.toLocalFile(); + QVERIFY(QFile::exists(file)); + //qDebug() << file; + KIO::Job *job = KIO::del(url, KIO::HideProgressInfo); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(file)); + } + qDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds"; +} + +void JobTest::deleteManyFilesTogether(bool using_fast_path) +{ + extern KIOCORE_EXPORT bool kio_resolve_local_urls; + kio_resolve_local_urls = !using_fast_path; + + QTime dt; + dt.start(); + const int numFiles = 100; // Use 1000 for performance testing + const QString baseDir = homeTmpDir(); + const QList urls = createManyFiles(baseDir, numFiles); + QCOMPARE(urls.count(), numFiles); + + //qDebug() << file; + KIO::Job *job = KIO::del(urls, KIO::HideProgressInfo); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + qDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds"; + + kio_resolve_local_urls = true; +} + +void JobTest::deleteManyFilesTogether() +{ + deleteManyFilesTogether(true); + deleteManyFilesTogether(false); +} + +void JobTest::rmdirEmpty() +{ + const QString dir = homeTmpDir() + "dir"; + QDir().mkdir(dir); + QVERIFY(QFile::exists(dir)); + KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir)); + QVERIFY(job->exec()); + QVERIFY(!QFile::exists(dir)); +} + +void JobTest::rmdirNotEmpty() +{ + const QString dir = homeTmpDir() + "dir"; + createTestDirectory(dir); + createTestDirectory(dir + "/subdir"); + KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir)); + QVERIFY(!job->exec()); + QVERIFY(QFile::exists(dir)); +} + +void JobTest::stat() +{ +#if 1 + const QString filePath = homeTmpDir() + "fileFromHome"; + createTestFile(filePath); + const QUrl url(QUrl::fromLocalFile(filePath)); + KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); + QVERIFY(job); + bool ok = job->exec(); + QVERIFY(ok); + // TODO set setSide, setDetails + const KIO::UDSEntry &entry = job->statResult(); + QVERIFY(!entry.isDir()); + QVERIFY(!entry.isLink()); + QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome")); + + // Compare what we get via kio_file and what we get when KFileItem stat()s directly + const KFileItem kioItem(entry, url); + const KFileItem fileItem(url); + QCOMPARE(kioItem.name(), fileItem.name()); + QCOMPARE(kioItem.url(), fileItem.url()); + QCOMPARE(kioItem.size(), fileItem.size()); + QCOMPARE(kioItem.user(), fileItem.user()); + QCOMPARE(kioItem.group(), fileItem.group()); + QCOMPARE(kioItem.mimetype(), fileItem.mimetype()); + QCOMPARE(kioItem.permissions(), fileItem.permissions()); + QCOMPARE(kioItem.time(KFileItem::ModificationTime), fileItem.time(KFileItem::ModificationTime)); + QCOMPARE(kioItem.time(KFileItem::AccessTime), fileItem.time(KFileItem::AccessTime)); + +#else + // Testing stat over HTTP + KIO::StatJob *job = KIO::stat(QUrl("http://www.kde.org"), KIO::HideProgressInfo); + QVERIFY(job); + bool ok = job->exec(); + QVERIFY(ok); + // TODO set setSide, setDetails + const KIO::UDSEntry &entry = job->statResult(); + QVERIFY(!entry.isDir()); + QVERIFY(!entry.isLink()); + QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString()); +#endif +} + +#ifndef Q_OS_WIN +void JobTest::statSymlink() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + createTestFile(filePath); + const QString symlink = otherTmpDir() + "link"; + QVERIFY(QFile(filePath).link(symlink)); + QVERIFY(QFile::exists(symlink)); + setTimeStamp(symlink, QDateTime::currentDateTime().addSecs(-20)); // differenciate link time and source file time + + const QUrl url(QUrl::fromLocalFile(symlink)); + KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); + QVERIFY(job); + bool ok = job->exec(); + QVERIFY(ok); + // TODO set setSide, setDetails + const KIO::UDSEntry &entry = job->statResult(); + QVERIFY(!entry.isDir()); + QVERIFY(entry.isLink()); + QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("link")); + + // Compare what we get via kio_file and what we get when KFileItem stat()s directly + const KFileItem kioItem(entry, url); + const KFileItem fileItem(url); + QCOMPARE(kioItem.name(), fileItem.name()); + QCOMPARE(kioItem.url(), fileItem.url()); + QVERIFY(kioItem.isLink()); + QVERIFY(fileItem.isLink()); + QCOMPARE(kioItem.linkDest(), fileItem.linkDest()); + QCOMPARE(kioItem.size(), fileItem.size()); + QCOMPARE(kioItem.user(), fileItem.user()); + QCOMPARE(kioItem.group(), fileItem.group()); + QCOMPARE(kioItem.mimetype(), fileItem.mimetype()); + QCOMPARE(kioItem.permissions(), fileItem.permissions()); + QCOMPARE(kioItem.time(KFileItem::ModificationTime), fileItem.time(KFileItem::ModificationTime)); + QCOMPARE(kioItem.time(KFileItem::AccessTime), fileItem.time(KFileItem::AccessTime)); +} +#endif + +void JobTest::mostLocalUrl() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + createTestFile(filePath); + KIO::StatJob *job = KIO::mostLocalUrl(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); + QVERIFY(job); + bool ok = job->exec(); + QVERIFY(ok); + QCOMPARE(job->mostLocalUrl().toLocalFile(), filePath); +} + +void JobTest::chmodFile() +{ + const QString filePath = homeTmpDir() + "fileForChmod"; + createTestFile(filePath); + KFileItem item(QUrl::fromLocalFile(filePath)); + const mode_t origPerm = item.permissions(); + mode_t newPerm = origPerm ^ S_IWGRP; + QVERIFY(newPerm != origPerm); + KFileItemList items; items << item; + KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QString(), QString(), false, KIO::HideProgressInfo); + job->setUiDelegate(0); + QVERIFY(job->exec()); + + KFileItem newItem(QUrl::fromLocalFile(filePath)); + QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(newPerm, 8)); + QFile::remove(filePath); +} + +void JobTest::chmodFileError() +{ + // chown(root) should fail + const QString filePath = homeTmpDir() + "fileForChmod"; + createTestFile(filePath); + KFileItem item(QUrl::fromLocalFile(filePath)); + const mode_t origPerm = item.permissions(); + mode_t newPerm = origPerm ^ S_IWGRP; + QVERIFY(newPerm != origPerm); + KFileItemList items; items << item; + KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QStringLiteral("root"), QString(), false, KIO::HideProgressInfo); + // Simulate the user pressing "Skip" in the dialog. + PredefinedAnswerJobUiDelegate extension; + extension.m_skipResult = KIO::S_SKIP; + job->setUiDelegateExtension(&extension); + + QVERIFY(job->exec()); + + QCOMPARE(extension.m_askSkipCalled, 1); + KFileItem newItem(QUrl::fromLocalFile(filePath)); + // We skipped, so the chmod didn't happen. + QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(origPerm, 8)); + QFile::remove(filePath); +} + +void JobTest::mimeType() +{ +#if 1 + const QString filePath = homeTmpDir() + "fileFromHome"; + createTestFile(filePath); + KIO::MimetypeJob *job = KIO::mimetype(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); + QVERIFY(job); + QSignalSpy spyMimeType(job, SIGNAL(mimetype(KIO::Job*,QString))); + bool ok = job->exec(); + QVERIFY(ok); + QCOMPARE(spyMimeType.count(), 1); + QCOMPARE(spyMimeType[0][0], QVariant::fromValue(static_cast(job))); + QCOMPARE(spyMimeType[0][1].toString(), QStringLiteral("application/octet-stream")); +#else + // Testing mimetype over HTTP + KIO::MimetypeJob *job = KIO::mimetype(QUrl("http://www.kde.org"), KIO::HideProgressInfo); + QVERIFY(job); + QSignalSpy spyMimeType(job, SIGNAL(mimetype(KIO::Job*,QString))); + bool ok = job->exec(); + QVERIFY(ok); + QCOMPARE(spyMimeType.count(), 1); + QCOMPARE(spyMimeType[0][0], QVariant::fromValue(static_cast(job))); + QCOMPARE(spyMimeType[0][1].toString(), QString("text/html")); +#endif +} + +void JobTest::mimeTypeError() +{ + // KIO::mimetype() on a file that doesn't exist + const QString filePath = homeTmpDir() + "doesNotExist"; + KIO::MimetypeJob *job = KIO::mimetype(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); + QVERIFY(job); + QSignalSpy spyMimeType(job, SIGNAL(mimetype(KIO::Job*,QString))); + QSignalSpy spyResult(job, SIGNAL(result(KJob*))); + bool ok = job->exec(); + QVERIFY(!ok); + QCOMPARE(spyMimeType.count(), 0); + QCOMPARE(spyResult.count(), 1); +} + +void JobTest::moveFileDestAlreadyExists() // #157601 +{ + const QString file1 = homeTmpDir() + "fileFromHome"; + createTestFile(file1); + const QString file2 = homeTmpDir() + "anotherFile"; + createTestFile(file2); + const QString existingDest = otherTmpDir() + "fileFromHome"; + createTestFile(existingDest); + + QList urls; urls << QUrl::fromLocalFile(file1) << QUrl::fromLocalFile(file2); + KIO::CopyJob *job = KIO::move(urls, QUrl::fromLocalFile(otherTmpDir()), KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + job->setAutoSkip(true); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QFile::exists(file1)); // it was skipped + QVERIFY(!QFile::exists(file2)); // it was moved +} + +void JobTest::moveDestAlreadyExistsAutoRename_data() +{ + QTest::addColumn("samePartition"); + QTest::addColumn("moveDirs"); + + QTest::newRow("files same partition") << true << false; + QTest::newRow("files other partition") << false << false; + QTest::newRow("dirs same partition") << true << true; + QTest::newRow("dirs other partition") << false << true; +} + +void JobTest::moveDestAlreadyExistsAutoRename() +{ + QFETCH(bool, samePartition); + QFETCH(bool, moveDirs); + + QString dir; + if (samePartition) { + dir = homeTmpDir() + "dir/"; + QVERIFY(QDir(dir).exists() || QDir().mkdir(dir)); + } else { + dir = otherTmpDir(); + } + moveDestAlreadyExistsAutoRename(dir, moveDirs); + + if (samePartition) { + // cleanup + KIO::Job *job = KIO::del(QUrl::fromLocalFile(dir), KIO::HideProgressInfo); + QVERIFY(job->exec()); + QVERIFY(!QFile::exists(dir)); + } +} + +void JobTest::moveDestAlreadyExistsAutoRename(const QString &destDir, bool moveDirs) // #256650 +{ + const QString prefix = moveDirs ? QStringLiteral("dir ") : QStringLiteral("file "); + QStringList sources; + const QString file1 = homeTmpDir() + prefix + "(1)"; + const QString file2 = homeTmpDir() + prefix + "(2)"; + const QString existingDest1 = destDir + prefix + "(1)"; + const QString existingDest2 = destDir + prefix + "(2)"; + sources << file1 << file2 << existingDest1 << existingDest2; + Q_FOREACH (const QString &source, sources) { + if (moveDirs) { + QVERIFY(QDir().mkdir(source)); + } else { + createTestFile(source); + } + } + + QList urls; urls << QUrl::fromLocalFile(file1) << QUrl::fromLocalFile(file2); + KIO::CopyJob *job = KIO::move(urls, QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + job->setAutoRename(true); + + //qDebug() << QDir(destDir).entryList(); + + bool ok = job->exec(); + + qDebug() << QDir(destDir).entryList(); + QVERIFY(ok); + QVERIFY(!QFile::exists(file1)); // it was moved + QVERIFY(!QFile::exists(file2)); // it was moved + QVERIFY(QFile::exists(existingDest1)); + QVERIFY(QFile::exists(existingDest2)); + const QString file3 = destDir + prefix + "(3)"; + const QString file4 = destDir + prefix + "(4)"; + QVERIFY(QFile::exists(file3)); + QVERIFY(QFile::exists(file4)); + if (moveDirs) { + QDir().rmdir(file1); + QDir().rmdir(file2); + QDir().rmdir(file3); + QDir().rmdir(file4); + } else { + QFile::remove(file1); + QFile::remove(file2); + QFile::remove(file3); + QFile::remove(file4); + } +} + +void JobTest::moveAndOverwrite() +{ + const QString sourceFile = homeTmpDir() + "fileFromHome"; + createTestFile(sourceFile); + QString existingDest = otherTmpDir() + "fileFromHome"; + createTestFile(existingDest); + + KIO::FileCopyJob *job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); + job->setUiDelegate(0); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(sourceFile)); // it was moved + +#ifndef Q_OS_WIN + // Now same thing when the target is a symlink to the source + createTestFile(sourceFile); + createTestSymlink(existingDest, QFile::encodeName(sourceFile)); + QVERIFY(QFile::exists(existingDest)); + job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); + job->setUiDelegate(0); + ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(sourceFile)); // it was moved + + // Now same thing when the target is a symlink to another file + createTestFile(sourceFile); + createTestFile(sourceFile + "2"); + createTestSymlink(existingDest, QFile::encodeName(sourceFile + "2")); + QVERIFY(QFile::exists(existingDest)); + job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); + job->setUiDelegate(0); + ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(sourceFile)); // it was moved + + // Now same thing when the target is a _broken_ symlink + createTestFile(sourceFile); + createTestSymlink(existingDest); + QVERIFY(!QFile::exists(existingDest)); // it exists, but it's broken... + job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); + job->setUiDelegate(0); + ok = job->exec(); + QVERIFY(ok); + QVERIFY(!QFile::exists(sourceFile)); // it was moved +#endif +} + +void JobTest::moveOverSymlinkToSelf() // #169547 +{ +#ifndef Q_OS_WIN + const QString sourceFile = homeTmpDir() + "fileFromHome"; + createTestFile(sourceFile); + const QString existingDest = homeTmpDir() + "testlink"; + createTestSymlink(existingDest, QFile::encodeName(sourceFile)); + QVERIFY(QFile::exists(existingDest)); + + KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + bool ok = job->exec(); + QVERIFY(!ok); + QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // and not ERR_IDENTICAL_FILES! + QVERIFY(QFile::exists(sourceFile)); // it not moved +#endif +} + +void JobTest::createSymlink() +{ +#ifdef Q_OS_WIN + QSKIP("Test skipped on Windows"); +#endif + const QString sourceFile = homeTmpDir() + "fileFromHome"; + createTestFile(sourceFile); + const QString destDir = homeTmpDir() + "dest"; + QVERIFY(QDir().mkpath(destDir)); + + KIO::CopyJob *job = KIO::link(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); + QVERIFY(job->exec()); + QVERIFY(QFileInfo::exists(sourceFile)); + const QString dest = destDir + "/fileFromHome"; + QVERIFY(QFileInfo(dest).isSymLink()); + QVERIFY(QDir(destDir).removeRecursively()); +} + +void JobTest::createSymlinkTargetDirDoesntExist() +{ +#ifdef Q_OS_WIN + QSKIP("Test skipped on Windows"); +#endif + const QString sourceFile = homeTmpDir() + "fileFromHome"; + createTestFile(sourceFile); + const QString destDir = homeTmpDir() + "dest/does/not/exist"; + + KIO::CopyJob *job = KIO::link(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); + QVERIFY(!job->exec()); + QCOMPARE(job->error(), static_cast(KIO::ERR_CANNOT_SYMLINK)); +} + +void JobTest::createSymlinkAsShouldSucceed() +{ +#ifdef Q_OS_WIN + QSKIP("Test skipped on Windows"); +#endif + const QString sourceFile = homeTmpDir() + "fileFromHome"; + createTestFile(sourceFile); + const QString dest = homeTmpDir() + "testlink"; + QFile::remove(dest); // just in case + + KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + QVERIFY(job->exec()); + QVERIFY(QFileInfo::exists(sourceFile)); + QVERIFY(QFileInfo(dest).isSymLink()); + QVERIFY(QFile::remove(dest)); +} + +void JobTest::createSymlinkAsShouldFailDirectoryExists() +{ +#ifdef Q_OS_WIN + QSKIP("Test skipped on Windows"); +#endif + const QString sourceFile = homeTmpDir() + "fileFromHome"; + createTestFile(sourceFile); + const QString dest = homeTmpDir() + "dest"; + QVERIFY(QDir().mkpath(dest)); // dest exists as a directory + + KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + QVERIFY(!job->exec()); + QCOMPARE(job->error(), (int)KIO::ERR_DIR_ALREADY_EXIST); + QVERIFY(QFileInfo::exists(sourceFile)); + QVERIFY(!QFileInfo::exists(dest + "/fileFromHome")); + QVERIFY(QDir().rmdir(dest)); +} + +void JobTest::createSymlinkAsShouldFailFileExists() +{ +#ifdef Q_OS_WIN + QSKIP("Test skipped on Windows"); +#endif + const QString sourceFile = homeTmpDir() + "fileFromHome"; + createTestFile(sourceFile); + const QString dest = homeTmpDir() + "testlink"; + QFile::remove(dest); // just in case + + // First time works + KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + QVERIFY(job->exec()); + QVERIFY(QFileInfo(dest).isSymLink()); + + // Second time fails (already exists) + job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + QVERIFY(!job->exec()); + QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); + QVERIFY(QFile::remove(dest)); +} + +void JobTest::createBrokenSymlink() +{ +#ifdef Q_OS_WIN + QSKIP("Test skipped on Windows"); +#endif + const QString sourceFile = "/does/not/exist"; + const QString dest = homeTmpDir() + "testlink"; + QFile::remove(dest); // just in case + KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + QVERIFY(job->exec()); + QVERIFY(QFileInfo(dest).isSymLink()); + + // Second time fails (already exists) + job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + QVERIFY(!job->exec()); + QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); + QVERIFY(QFile::remove(dest)); +} + +void JobTest::multiGet() +{ + const int numFiles = 10; + const QString baseDir = homeTmpDir(); + const QList urls = createManyFiles(baseDir, numFiles); + QCOMPARE(urls.count(), numFiles); + + //qDebug() << file; + KIO::MultiGetJob *job = KIO::multi_get(0, urls.at(0), KIO::MetaData()); // TODO: missing KIO::HideProgressInfo + QSignalSpy spyData(job, SIGNAL(data(long,QByteArray))); + QSignalSpy spyMimeType(job, SIGNAL(mimetype(long,QString))); + QSignalSpy spyResultId(job, SIGNAL(result(long))); + QSignalSpy spyResult(job, SIGNAL(result(KJob*))); + job->setUiDelegate(0); + + for (int i = 1; i < numFiles; ++i) { + const QUrl url = urls.at(i); + job->get(i, url, KIO::MetaData()); + } + //connect(job, &KIO::MultiGetJob::result, [=] (long id) { qDebug() << "ID I got" << id;}); + //connect(job, &KJob::result, [this](KJob* ) {qDebug() << "END";}); + + bool ok = job->exec(); + QVERIFY(ok); + + QCOMPARE(spyResult.count(), 1); + QCOMPARE(spyResultId.count(), numFiles); + QCOMPARE(spyMimeType.count(), numFiles); + QCOMPARE(spyData.count(), numFiles * 2); + for (int i = 0; i < numFiles; ++i) { + QCOMPARE(spyResultId.at(i).at(0).toInt(), i); + QCOMPARE(spyMimeType.at(i).at(0).toInt(), i); + QCOMPARE(spyMimeType.at(i).at(1).toString(), QStringLiteral("text/plain")); + QCOMPARE(spyData.at(i * 2).at(0).toInt(), i); + QCOMPARE(QString(spyData.at(i * 2).at(1).toByteArray()), QStringLiteral("Hello")); + QCOMPARE(spyData.at(i * 2 + 1).at(0).toInt(), i); + QCOMPARE(QString(spyData.at(i * 2 + 1).at(1).toByteArray()), QLatin1String("")); + } +} + diff --git a/autotests/jobtest.h b/autotests/jobtest.h new file mode 100644 index 0000000..a09a988 --- /dev/null +++ b/autotests/jobtest.h @@ -0,0 +1,136 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef JOBTEST_H +#define JOBTEST_H + +#include +#include +#include + +class JobTest : public QObject +{ + Q_OBJECT + +public: + JobTest() {} + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + // Local tests (kio_file only) + void storedGet(); + void put(); + void storedPut(); + void storedPutIODevice(); + void storedPutIODeviceFile(); + void storedPutIODeviceTempFile(); + void storedPutIODeviceSlowDevice(); + void copyFileToSamePartition(); + void copyDirectoryToSamePartition(); + void copyDirectoryToExistingDirectory(); + void copyFileToOtherPartition(); + void copyDirectoryToOtherPartition(); + void copyRelativeSymlinkToSamePartition(); + void copyAbsoluteSymlinkToOtherPartition(); + void copyFolderWithUnaccessibleSubfolder(); + void listRecursive(); + void listFile(); + void killJob(); + void killJobBeforeStart(); + void deleteJobBeforeStart(); + void directorySize(); + void directorySizeError(); + void moveFileToSamePartition(); + void moveDirectoryToSamePartition(); + void moveDirectoryIntoItself(); + void moveFileToOtherPartition(); + void moveSymlinkToOtherPartition(); + void moveDirectoryToOtherPartition(); + void moveFileNoPermissions(); + void moveDirectoryNoPermissions(); + void deleteFile(); + void deleteDirectory(); + void deleteSymlink(); + void deleteManyDirs(); + void deleteManyFilesIndependently(); + void deleteManyFilesTogether(); + void rmdirEmpty(); + void rmdirNotEmpty(); + void stat(); +#ifndef Q_OS_WIN + void statSymlink(); +#endif + void mostLocalUrl(); + void chmodFile(); + void chmodFileError(); + void mimeType(); + void mimeTypeError(); + void calculateRemainingSeconds(); + void moveFileDestAlreadyExists(); + void moveDestAlreadyExistsAutoRename_data(); + void moveDestAlreadyExistsAutoRename(); + + void moveAndOverwrite(); + void moveOverSymlinkToSelf(); + void createSymlink(); + void createSymlinkTargetDirDoesntExist(); + void createSymlinkAsShouldSucceed(); + void createSymlinkAsShouldFailDirectoryExists(); + void createSymlinkAsShouldFailFileExists(); + void createBrokenSymlink(); + + // Remote tests + //void copyFileToSystem(); + + void getInvalidUrl(); + void multiGet(); + +Q_SIGNALS: + void exitLoop(); + +protected Q_SLOTS: + void slotEntries(KIO::Job *, const KIO::UDSEntryList &lst); + void slotGetResult(KJob *); + void slotDataReq(KIO::Job *, QByteArray &); + void slotResult(KJob *); + void slotMimetype(KIO::Job *, const QString &); + +private: + void enterLoop(); + enum { AlreadyExists = 1 }; + void copyLocalFile(const QString &src, const QString &dest); + void copyLocalDirectory(const QString &src, const QString &dest, int flags = 0); + void moveLocalFile(const QString &src, const QString &dest); + void moveLocalDirectory(const QString &src, const QString &dest); + //void copyFileToSystem( bool resolve_local_urls ); + void deleteSymlink(bool using_fast_path); + void deleteManyDirs(bool using_fast_path); + void deleteManyFilesTogether(bool using_fast_path); + void moveDestAlreadyExistsAutoRename(const QString &destDir, bool moveDirs); + + int m_result; + QByteArray m_data; + QStringList m_names; + int m_dataReqCount; + QString m_mimetype; +}; + +#endif diff --git a/autotests/kacltest.cpp b/autotests/kacltest.cpp new file mode 100644 index 0000000..c8d71d8 --- /dev/null +++ b/autotests/kacltest.cpp @@ -0,0 +1,218 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Till Adam + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kacltest.h" + +#include +#include + +#include +#include + +#include + +// The code comes partly from kdebase/kioslave/trash/testtrash.cpp + +QTEST_MAIN(KACLTest) + +static const QString s_testACL(QStringLiteral("user::rw-\nuser:bin:rwx\ngroup::rw-\nmask::rwx\nother::r--\n")); +static const QString s_testACL2(QStringLiteral("user::rwx\nuser:bin:rwx\ngroup::rw-\ngroup:users:r--\ngroup:audio:--x\nmask::r-x\nother::r--\n")); +static const QString s_testACLEffective(QStringLiteral("user::rwx\nuser:bin:rwx #effective:r-x\ngroup::rw- #effective:r--\ngroup:audio:--x\ngroup:users:r--\nmask::r-x\nother::r--\n")); + +KACLTest::KACLTest() + : m_acl(s_testACL) +{ +} + +void KACLTest::initTestCase() +{ + m_acl2.setACL(s_testACL2); +} + +void KACLTest::testAsString() +{ + QCOMPARE(m_acl.asString(), s_testACL); +} + +void KACLTest::testSetACL() +{ + QCOMPARE(m_acl2.asString().simplified(), s_testACLEffective.simplified()); +} + +void KACLTest::testGetOwnerPermissions() +{ + QCOMPARE(int(m_acl.ownerPermissions()), 6); +} + +void KACLTest::testGetOwningGroupPermissions() +{ + QCOMPARE(int(m_acl.owningGroupPermissions()), 6); +} + +void KACLTest::testGetOthersPermissions() +{ + QCOMPARE(int(m_acl.othersPermissions()), 4); +} + +void KACLTest::testGetMaskPermissions() +{ + bool exists = false; + int mask = m_acl.maskPermissions(exists); + QVERIFY(exists); + QCOMPARE(mask, 7); +} + +void KACLTest::testGetAllUserPermissions() +{ + ACLUserPermissionsList list = m_acl.allUserPermissions(); + ACLUserPermissionsConstIterator it = list.constBegin(); + QString name; + int permissions = 0; + int count = 0; + while (it != list.constEnd()) { + name = (*it).first; + permissions = (*it).second; + ++it; + ++count; + } + QCOMPARE(count, 1); + QCOMPARE(name, QStringLiteral("bin")); + QCOMPARE(permissions, 7); +} + +void KACLTest::testGetAllGroupsPermissions() +{ + ACLGroupPermissionsList list = m_acl2.allGroupPermissions(); + ACLGroupPermissionsConstIterator it = list.constBegin(); + QString name; + int permissions; + int count = 0; + while (it != list.constEnd()) { + name = (*it).first; + permissions = (*it).second; + // setACL sorts them alphabetically ... + if (count == 0) { + QCOMPARE(name, QStringLiteral("audio")); + QCOMPARE(permissions, 1); + } else if (count == 1) { + QCOMPARE(name, QStringLiteral("users")); + QCOMPARE(permissions, 4); + } + ++it; + ++count; + } + QCOMPARE(count, 2); +} + +void KACLTest::testIsExtended() +{ + KACL dukeOfMonmoth(s_testACL); + QVERIFY(dukeOfMonmoth.isExtended()); + KACL earlOfUpnor(QStringLiteral("user::r--\ngroup::r--\nother::r--\n")); + QVERIFY(!earlOfUpnor.isExtended()); +} + +void KACLTest::testOperators() +{ + KACL dukeOfMonmoth(s_testACL); + KACL JamesScott(s_testACL); + KACL earlOfUpnor(s_testACL2); + QVERIFY(!(dukeOfMonmoth == earlOfUpnor)); + QVERIFY(dukeOfMonmoth != earlOfUpnor); + QVERIFY(dukeOfMonmoth != earlOfUpnor); + QVERIFY(!(dukeOfMonmoth != JamesScott)); +} + +void KACLTest::testSettingBasic() +{ + KACL CharlesII(s_testACL); + CharlesII.setOwnerPermissions(7); // clearly + CharlesII.setOwningGroupPermissions(0); + CharlesII.setOthersPermissions(0); + QCOMPARE(int(CharlesII.ownerPermissions()), 7); + QCOMPARE(int(CharlesII.owningGroupPermissions()), 0); + QCOMPARE(int(CharlesII.othersPermissions()), 0); +} + +void KACLTest::testSettingExtended() +{ + KACL CharlesII(s_testACL); + CharlesII.setMaskPermissions(7); // clearly + bool dummy = false; + QCOMPARE(int(CharlesII.maskPermissions(dummy)), 7); + + const QString expected(QStringLiteral("user::rw-\nuser:root:rwx\nuser:bin:r--\ngroup::rw-\nmask::rwx\nother::r--\n")); + + ACLUserPermissionsList users; + ACLUserPermissions user = qMakePair(QStringLiteral("root"), (unsigned short)7); + users.append(user); + user = qMakePair(QStringLiteral("bin"), (unsigned short)4); + users.append(user); + CharlesII.setAllUserPermissions(users); + QCOMPARE(CharlesII.asString(), expected); + + CharlesII.setACL(s_testACL); // reset + // it already has an entry for bin, let's change it + CharlesII.setNamedUserPermissions(QStringLiteral("bin"), 4); + CharlesII.setNamedUserPermissions(QStringLiteral("root"), 7); + QCOMPARE(CharlesII.asString(), expected); + + // groups, all and named + + const QString expected2(QStringLiteral("user::rw-\nuser:bin:rwx\ngroup::rw-\ngroup:audio:-wx\ngroup:users:r--\nmask::rwx\nother::r--\n")); + CharlesII.setACL(s_testACL); // reset + ACLGroupPermissionsList groups; + ACLGroupPermissions group = qMakePair(QStringLiteral("audio"), (unsigned short)3); + groups.append(group); + group = qMakePair(QStringLiteral("users"), (unsigned short)4); + groups.append(group); + CharlesII.setAllGroupPermissions(groups); + QCOMPARE(CharlesII.asString(), expected2); + + CharlesII.setACL(s_testACL); // reset + CharlesII.setNamedGroupPermissions(QStringLiteral("audio"), 3); + CharlesII.setNamedGroupPermissions(QStringLiteral("users"), 4); + QCOMPARE(CharlesII.asString(), expected2); +} + +void KACLTest::testSettingErrorHandling() +{ + KACL foo(s_testACL); + bool v = foo.setNamedGroupPermissions(QStringLiteral("audio"), 7); // existing group + QVERIFY(v); + v = foo.setNamedGroupPermissions(QStringLiteral("jongel"), 7); // non-existing group + QVERIFY(!v); + + v = foo.setNamedUserPermissions(QStringLiteral("bin"), 7); // existing user + QVERIFY(v); + v = foo.setNamedUserPermissions(QStringLiteral("jongel"), 7); // non-existing user + QVERIFY(!v); +} + +void KACLTest::testNewMask() +{ + KACL CharlesII(QStringLiteral("user::rw-\ngroup::rw-\nother::rw\n")); + bool dummy = false; + CharlesII.maskPermissions(dummy); + QVERIFY(!dummy); + + CharlesII.setMaskPermissions(6); + QCOMPARE(int(CharlesII.maskPermissions(dummy)), 6); + QVERIFY(dummy); // mask exists now +} diff --git a/autotests/kacltest.h b/autotests/kacltest.h new file mode 100644 index 0000000..9f4451a --- /dev/null +++ b/autotests/kacltest.h @@ -0,0 +1,54 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Till Adam + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KACLTEST_H +#define KACLTEST_H + +#include +#include + +class KACLTest : public QObject +{ + Q_OBJECT +public: + KACLTest(); + +private Q_SLOTS: + void initTestCase(); + void testAsString(); + void testSetACL(); + void testGetOwnerPermissions(); + void testGetOwningGroupPermissions(); + void testGetOthersPermissions(); + void testGetMaskPermissions(); + void testGetAllUserPermissions(); + void testGetAllGroupsPermissions(); + void testIsExtended(); + void testOperators(); + void testSettingBasic(); + void testSettingExtended(); + void testSettingErrorHandling(); + void testNewMask(); + +private: + KACL m_acl; + KACL m_acl2; +}; + +#endif diff --git a/autotests/kcookiejar/CMakeLists.txt b/autotests/kcookiejar/CMakeLists.txt new file mode 100644 index 0000000..007f05b --- /dev/null +++ b/autotests/kcookiejar/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt5Test REQUIRED) + +include(ECMAddTests) + +########### next target ############### + + +# linking to Qt5::Gui is only needed for the include paths +ecm_add_test(kcookiejartest.cpp + NAME_PREFIX "kioslave-" + LINK_LIBRARIES Qt5::Test Qt5::Gui KF5::KIOCore +) + +########### install files ############### + + + + + diff --git a/autotests/kcookiejar/cookie.test b/autotests/kcookiejar/cookie.test new file mode 100644 index 0000000..1db73b7 --- /dev/null +++ b/autotests/kcookiejar/cookie.test @@ -0,0 +1,188 @@ +## Check setting of cookies +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/ Cookie: some_value=value1 +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/" +CHECK http://a.b.c/ Cookie: some_value=value2 +## Check if clearing cookie jar works +CLEAR COOKIES +CHECK http://w.y.z/ +CHECK http://a.b.c/ +## Check cookie syntax +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value with spaces +CHECK http://w.y.z/ Cookie: some_value=value with spaces +COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value" +CHECK http://a.b.c/ Cookie: some_value="quoted value" +# Without a = sign, the cookie gets interpreted as the value for a cookie with no name +# This is what IE and Netscape does +COOKIE ASK http://a.b.c/ Set-Cookie: some_value +# Note: order in the expected list does not matter. +CHECK http://a.b.c/ Cookie: some_value="quoted value"; some_value +COOKIE ASK http://a.b.c/ Set-Cookie: some_other_value +CHECK http://a.b.c/ Cookie: some_value="quoted value"; some_other_value +CLEAR COOKIES +# This doesn't work with old-style netscape cookies, it should work with RFC2965 cookies +COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value; and such" +# IE & Netscape does this: +CHECK http://a.b.c/ Cookie: some_value="quoted value +# Mozilla does: +# CHECK http://a.b.c/ Cookie: some_value="quoted value; and such" +# COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value; +# CHECK http://a.b.c/ Cookie: some_value= +# Note that we parse RFC2965 cookies like Mozilla does +CLEAR COOKIES +## Check if deleting cookies works +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/ Cookie: some_value=value1 +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%LASTYEAR% +CHECK http://w.y.z/ +## Check if updating cookies works +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value2; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/ Cookie: some_value=value3 +## Check if multiple cookies work +COOKIE ASK http://w.y.z/ Set-Cookie: some_value2=foobar; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/ Cookie: some_value=value3; some_value2=foobar +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=; Path="/"; expires=%LASTYEAR% +CHECK http://w.y.z/ Cookie: some_value2=foobar +CLEAR COOKIES +## Check if path restrictions work +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR% +CHECK http://w.y.z/ +CHECK http://w.y.z/Foo Cookie: some_value=value1 +CHECK http://w.y.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y.z/Foo/bar Cookie: some_value=value1 +CLEAR COOKIES +## Check if default path works +# RFC2965 says that we should default to the URL path, but netscape cookies default to / +COOKIE ASK http://w.y.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +CHECK http://w.y.z/ +CHECK http://w.y.z/Foo Cookie: some_value=value1 +CHECK http://w.y.z/FooBar +CHECK http://w.y.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y.z/Foo/bar Cookie: some_value=value1 +CLEAR COOKIES +## Check if cookies are correctly ordered based on path +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR% +COOKIE ASK http://w.y.z/ Set-Cookie: some_value2=value2; Path="/Foo/Bar"; expires=%NEXTYEAR% +CHECK http://w.y.z/Foo/Bar Cookie: some_value2=value2; some_value=value1 +COOKIE ASK http://w.y.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3 +CLEAR COOKIES +## Check cookies with same name but different paths +COOKIE ASK http://w.y.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +COOKIE ASK http://w.y.z/Bar/ Set-Cookie: some_value=value2; expires=%NEXTYEAR% +CHECK http://w.y.z/Foo/Bar Cookie: some_value=value1 +CHECK http://w.y.z/Bar/Foo Cookie: some_value=value2 +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value3; expires=%NEXTYEAR% +CHECK http://w.y.z/Foo/Bar Cookie: some_value=value1; some_value=value3 +## Check secure cookie handling +COOKIE ASK https://secure.y.z/ Set-Cookie: some_value2=value2; Path="/"; expires=%NEXTYEAR%; secure +CHECK https://secure.y.z/Foo/bar Cookie: some_value2=value2 +CHECK http://secure.y.z/Foo/bar +CLEAR COOKIES +COOKIE ASK http://secure.y.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR%; secure +CHECK https://secure.y.z/Foo/bar Cookie: some_value3=value3 +CHECK http://secure.y.z/Foo/bar +CLEAR COOKIES +## Check domain restrictions #1 +COOKIE ASK http://www.acme.com/ Set-Cookie: some_value=value1; Domain=".acme.com"; expires=%NEXTYEAR% +CHECK http://www.acme.com/ Cookie: some_value=value1 +CHECK http://www.abc.com/ +CHECK http://frop.acme.com/ Cookie: some_value=value1 +CLEAR COOKIES +## Check domain restrictions #2 +COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain=".novell.com"; expires=%NEXTYEAR% +CHECK http://novell.com/ Cookie: some_value=value1 +CHECK http://www.novell.com/ Cookie: some_value=value1 +CLEAR COOKIES +COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain="novell.com"; expires=%NEXTYEAR% +CHECK http://novell.com/ Cookie: some_value=value1 +CHECK http://www.novell.com/ Cookie: some_value=value1 +CLEAR COOKIES +## Check domain restrictions #3 +COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +CHECK http://novell.com/ Cookie: some_value=value1 +# FIXME: Allegedly IE sends cookies to sub-domains as well! +# See e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=223027 +CHECK http://www.novell.com/ +CLEAR COOKIES +## Check domain restrictions #4 +COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain=".com"; expires=%NEXTYEAR% +CHECK http://novell.com/ Cookie: some_value=value1 +# If the specified domain is too broad, we default to host only +CHECK http://www.novell.com/ +CHECK http://com/ +CHECK http://sun.com/ +## Check domain restrictions #5 +CLEAR COOKIES +COOKIE ASK http://novell.co.uk/ Set-Cookie: some_value=value1; Domain=".co.uk"; expires=%NEXTYEAR% +CHECK http://novell.co.uk/ Cookie: some_value=value1 +# If the specified domain is too broad, we default to host only +CHECK http://www.novell.co.uk/ +CHECK http://co.uk/ +CHECK http://sun.co.uk/ +COOKIE ASK http://x.y.z.foobar.com/ Set-Cookie: set_by=x.y.z.foobar.com; Domain=".foobar.com"; expires=%NEXTYEAR% +CHECK http://x.y.z.foobar.com/ Cookie: set_by=x.y.z.foobar.com +CHECK http://y.z.foobar.com/ Cookie: set_by=x.y.z.foobar.com +CHECK http://z.foobar.com/ Cookie: set_by=x.y.z.foobar.com +CHECK http://www.foobar.com/ Cookie: set_by=x.y.z.foobar.com +CHECK http://foobar.com/ Cookie: set_by=x.y.z.foobar.com +CLEAR COOKIES +## Check domain restrictions #6 +COOKIE ASK http://x.y.z.frop.com/ Set-Cookie: set_by=x.y.z.frop.com; Domain=".foobar.com"; expires=%NEXTYEAR% +COOKIE ASK http://x.y.z.frop.com/ Set-Cookie: set_by2=x.y.z.frop.com; Domain=".com"; expires=%NEXTYEAR% +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CLEAR COOKIES +## Check domain restrictions #7 +COOKIE ASK http://frop.com/ Set-Cookie: set_by=x.y.z.frop.com; Domain=".foobar.com"; expires=%NEXTYEAR% +COOKIE ASK http://frop.com/ Set-Cookie: set_by2=x.y.z.frop.com; Domain=".com"; expires=%NEXTYEAR% +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CLEAR COOKIES +## Check domain restrictions #8 +CONFIG AcceptSessionCookies true +COOKIE ACCEPT http://www.foobar.com Set-Cookie: from=foobar.com; domain=bar.com; Path="/" +CHECK http://bar.com +CLEAR CONFIG +CLEAR COOKIES +## Check cookies with IP address hostnames +COOKIE ASK http://192.168.0.1 Set-Cookie: name1=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://192.168.0.1 Set-Cookie: name11=value11; domain="test.local"; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://192.168.0.1:8080 Set-Cookie: name2=value2; Path="/"; expires=%NEXTYEAR% +COOKIE ASK https://192.168.0.1 Set-Cookie: name3=value3; Path="/"; expires=%NEXTYEAR%; secure +CHECK http://192.168.0.1 Cookie: name1=value1; name11=value11; name2=value2 +CHECK http://192.168.0.1:8080 Cookie: name1=value1; name11=value11; name2=value2 +CHECK https://192.168.0.1 Cookie: name1=value1; name11=value11; name2=value2; name3=value3 +CHECK http://192.168.0.10 +CHECK http://192.168.0 +CLEAR COOKIES +## Check expiration dates for the Y2K38 problem +COOKIE ASK http://foo.bar Set-Cookie: name=value;expires=Tue, 06-Dec-2039 00:30:42 GMT;path="/" +CHECK http://foo.bar Cookie: name=value +CLEAR COOKIES +## Check non-standard expiration dates (BR# 145244) +COOKIE ASK http://foo.bar Set-Cookie: name=value; expires=Sat Sep 12 07:00:00 2020 GMT; path="/" +COOKIE ASK http://foo.bar Set-Cookie: name1=value1; expires=Thu, 01 Jan 1970 00:00:00 GMT; path="/" +COOKIE ASK http://foo.bar Set-Cookie: name2=value2; expires=Sat Sep 12 2020 07:00:00 GMT; path="/" +CHECK http://foo.bar Cookie: name=value; name2=value2 +CLEAR COOKIES +## Check path restrictions +COOKIE ASK http://a.b.c/app1 Set-Cookie: name=value; Path="/app1"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/app2 Set-Cookie: name1=value1; Path="/app2"; expires=%NEXTYEAR% +CHECK http://a.b.c/app1 Cookie: name=value +## Check invalid weekday value in expire headers (BR# 298660) +COOKIE ASK http://foo.bar Set-Cookie: name=value; expires=Thu, 01 Jan 1970 00:00:00 GMT; path="/" +COOKIE ASK http://foo.bar Set-Cookie: name1=value1; expires=Thu, 30 Dec 2037 00:00:00 GMT; path="/" +CLEAR SESSIONCOOKIES +CHECK http://foo.bar Cookie: name1=value1 +CLEAR COOKIES +## Check JSON formatted cookie values (QTBUG-26002) +COOKIE ASK http://www.foo.bar Set-Cookie: name={"value":"null","value2":"null","value3":"null"}; domain=.foo.bar; path=/ +CHECK http://www.foo.bar Cookie: name={"value":"null","value2":"null","value3":"null"} diff --git a/autotests/kcookiejar/cookie_rfc.test b/autotests/kcookiejar/cookie_rfc.test new file mode 100644 index 0000000..25ad268 --- /dev/null +++ b/autotests/kcookiejar/cookie_rfc.test @@ -0,0 +1,172 @@ +## Check setting of cookies +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600 +# Although the examples in RFC2965 uses $Version="1" the syntax description suggests that +# such quotes are not allowed, KDE BR59990 reports that the Sun Java server fails to handle +# cookies that use $Version="1" +CHECK http://w.y.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value="value2"; Version=1; Path="/" +CHECK http://a.b.c/ Cookie: $Version=1; some_value="value2"; $Path="/" +## Check if clearing cookie jar works +CLEAR COOKIES +CHECK http://w.y.z/ +CHECK http://a.b.c/ +## Check cookie syntax +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value with spaces"; Version=1 +CHECK http://w.y.z/ Cookie: $Version=1; some_value="value with spaces" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value ="extra space 1"; Version=1 +CHECK http://w.y.z/ Cookie: $Version=1; some_value="extra space 1" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value= "extra space 2"; Version=1 +CHECK http://w.y.z/ Cookie: $Version=1; some_value="extra space 2" +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=unquoted; Version=1 +CHECK http://a.b.c/ Cookie: $Version=1; some_value=unquoted +# Note that we parse this different for Netscape-style cookies! +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value="quoted value; and such"; Version=1; +CHECK http://a.b.c/ Cookie: $Version=1; some_value="quoted value; and such" +CLEAR COOKIES +## Check if deleting cookies works #1 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600 +CHECK http://w.y.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/"; Max-Age=0 +CHECK http://w.y.z/ +## Check if updating cookies works +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Max-Age=3600 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600 +CHECK http://w.y.z/ Cookie: $Version=1; some_value=value3; $Path="/" +## Check if multiple cookies work +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value2=foobar; Version=1; Path="/"; Max-Age=3600 +CHECK http://w.y.z/ Cookie: $Version=1; some_value=value3; $Path="/"; some_value2=foobar; $Path="/" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0 +CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CLEAR COOKIES +## Check if we prepend domain with a dot +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Domain=.y.z; Max-Age=3600 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Domain=y.z.; Max-Age=3600 +CHECK http://w.y.z/ Cookie: $Version=1; some_value=value3; $Path="/"; $Domain=".y.z" +CLEAR COOKIES +## Check if multiple cookies on a single line work +## FIXME +#COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600, some_value2=foobar; Version=1; Path="/"; Max-Age=3600 +# CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"; some_value=value3; $Path="/" +# COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0 +# CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CLEAR COOKIES +## Check if path restrictions work +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600 +CHECK http://w.y.z/ +CHECK http://w.y.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo" +CLEAR COOKIES +## Check if default path works +# RFC2965 says that we should default to the URL path +COOKIE ASK http://w.y.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +CHECK http://w.y.z/ +CHECK http://w.y.z/Foo Cookie: $Version=1; some_value=value1 +CHECK http://w.y.z/FooBar +CHECK http://w.y.z/Foo/ Cookie: $Version=1; some_value=value1 +CHECK http://w.y.z/Foo/bar Cookie: $Version=1; some_value=value1 +CLEAR COOKIES +## Check if cookies are correctly ordered based on path +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/Foo/Bar"; Max-Age=3600 +CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600 +CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/" +CLEAR COOKIES +## Check cookies with same name but different paths +COOKIE ASK http://w.y.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +COOKIE ASK http://w.y.z/Bar/ Set-Cookie2: some_value=value2; Version=1; Max-Age=3600 +CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value=value1 +CHECK http://w.y.z/Bar/Foo Cookie: $Version=1; some_value=value2 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Max-Age=3600 +CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3 +CLEAR COOKIES +## Check port selection handling (rfc 2965 3.3.4) +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Domain=.y.z; Port +CHECK http://foo.y.z/ Cookie: $Version=1; some_value=value1; $Domain=".y.z"; $Port +CHECK http://foo.y.z:8080 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Domain=.y.z; Port="80 8080 443" +CHECK http://foo.y.z/ Cookie: $Version=1; some_value=value1; $Domain=".y.z"; $Port="80 8080 443" +CHECK http://foo.y.z:8080 Cookie: $Version=1; some_value=value1; $Domain=".y.z"; $Port="80 8080 443" +CHECK http://foo.y.z:443 Cookie: $Version=1; some_value=value1; $Domain=".y.z"; $Port="80 8080 443" +CHECK http://w.y.z:3129 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Domain=.y.z +CHECK http://w.y.z:80 Cookie: $Version=1; some_value=value1; $Domain=".y.z" +CHECK http://w.y.z:443 Cookie: $Version=1; some_value=value1; $Domain=".y.z" +CHECK http://w.y.z:3129 Cookie: $Version=1; some_value=value1; $Domain=".y.z" +CHECK http://w.y.z:8080 Cookie: $Version=1; some_value=value1; $Domain=".y.z" +CLEAR COOKIES +## Check secure cookie handling +COOKIE ASK https://secure.y.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/"; Max-Age=3600; Secure +CHECK https://secure.y.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/" +CHECK http://secure.y.z/Foo/bar +CLEAR COOKIES +COOKIE ASK http://secure.y.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600; Secure +CHECK https://secure.y.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/" +CHECK http://secure.y.z/Foo/bar +CLEAR COOKIES +COOKIE ASK https://secure.y.z/ Set-Cookie: some_value=value; Path="/"; Max-Age=3600; +CHECK https://secure.y.z/Foo/bar Cookie: some_value=value +CHECK http://secure.y.z/Foo/bar Cookie: some_value=value +CLEAR COOKIES +COOKIE ASK http://secure.y.z/ Set-Cookie: some_value=value; Path="/"; Max-Age=3600; +CHECK https://secure.y.z/Foo/bar Cookie: some_value=value +CHECK http://secure.y.z/Foo/bar Cookie: some_value=value +CLEAR COOKIES +## Check domain restrictions #1 +COOKIE ASK http://www.acme.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".acme.com"; Max-Age=3600 +CHECK http://www.acme.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme.com" +CHECK http://www.abc.com/ +CHECK http://frop.acme.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme.com" +CLEAR COOKIES +## Check domain restrictions #2 +COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".novell.com"; Max-Age=3600 +CHECK http://novell.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell.com" +CHECK http://www.novell.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell.com" +CLEAR COOKIES +## Check domain restrictions #3 +COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +CHECK http://novell.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell.com/ +CLEAR COOKIES +## Check domain restrictions #4 +COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".com"; Max-Age=3600 +# If the specified domain is too broad, we ignore the Domain +# FIXME: RFC2965 says we should ignore the cookie completely +CHECK http://novell.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell.com/ +CHECK http://com/ +CHECK http://sun.com/ +## Check domain restrictions #5 +CLEAR COOKIES +COOKIE ASK http://novell.co.uk/ Set-Cookie2: some_value=value1; Version=1; Domain=".co.uk"; Max-Age=3600 +# If the specified domain is too broad, we default to host only +# FIXME: RFC2965 says we should ignore the cookie completely +CHECK http://novell.co.uk/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell.co.uk/ +CHECK http://co.uk/ +CHECK http://sun.co.uk/ +COOKIE ASK http://x.y.z.foobar.com/ Set-Cookie2: set_by=x.y.z.foobar.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +CHECK http://x.y.z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CHECK http://y.z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CHECK http://z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CHECK http://www.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CHECK http://foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CLEAR COOKIES +## Check domain restrictions #6 +COOKIE ASK http://x.y.z.frop.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +COOKIE ASK http://x.y.z.frop.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600 +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CLEAR COOKIES +## Check domain restrictions #7 +COOKIE ASK http://frop.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +COOKIE ASK http://frop.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600 +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ diff --git a/autotests/kcookiejar/cookie_saving.test b/autotests/kcookiejar/cookie_saving.test new file mode 100644 index 0000000..28dea3c --- /dev/null +++ b/autotests/kcookiejar/cookie_saving.test @@ -0,0 +1,434 @@ +## Check setting of cookies +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/" +## Check if clearing cookie jar works +CLEAR COOKIES +## Check cookie syntax +COOKIE ASK http://w.y1.z/ Set-Cookie: some_value=value with spaces; expires=%NEXTYEAR% +COOKIE ASK http://a.b1.c/ Set-Cookie: some_value="quoted value"; expires=%NEXTYEAR% +# Without a = sign, the cookie gets interpreted as the value for a cookie with no name +# This is what IE and Netscape does +COOKIE ASK http://a.b1.c/ Set-Cookie: some_value +COOKIE ASK http://a.b1.c/ Set-Cookie: some_other_value; expires=%NEXTYEAR% +# This doesn't work with old-style netscape cookies, it should work with RFC2965 cookies +COOKIE ASK http://a.b2.c/ Set-Cookie: some_value="quoted value; and such"; expires=%NEXTYEAR% +# IE & Netscape does this: +## Check if deleting cookies works +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value1; Path="/"; expires=%LASTYEAR% +## Check if updating cookies works +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value2; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR% +## Check if multiple cookies work +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value2=foobar; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=; Path="/"; expires=%LASTYEAR% +## Check if path restrictions work +COOKIE ASK http://w.y4.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR% +## Check if default path works +COOKIE ASK http://w.y5.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +## Check if cookies are correctly ordered based on path +COOKIE ASK http://w.y6.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR% +COOKIE ASK http://w.y6.z/ Set-Cookie: some_value2=value2; Path="/Foo/Bar"; expires=%NEXTYEAR% +COOKIE ASK http://w.y6.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR% +## Check cookies with same name but different paths +COOKIE ASK http://w.y7.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +COOKIE ASK http://w.y7.z/Bar/ Set-Cookie: some_value=value2; expires=%NEXTYEAR% +COOKIE ASK http://w.y7.z/ Set-Cookie: some_value=value3; expires=%NEXTYEAR% +## Check secure cookie handling +COOKIE ASK https://secure.y7.z/ Set-Cookie: some_value2=value2; Path="/"; expires=%NEXTYEAR%; secure +COOKIE ASK http://secure.y8.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR%; secure +## Check domain restrictions #1 +COOKIE ASK http://www.acme9.com/ Set-Cookie: some_value=value1; Domain=".acme9.com"; expires=%NEXTYEAR% +## Check domain restrictions #2 +COOKIE ASK http://novell10.com/ Set-Cookie: some_value=value1; Domain=".novell10.com"; expires=%NEXTYEAR% +COOKIE ASK http://novell11.com/ Set-Cookie: some_value=value1; Domain="novell11.com"; expires=%NEXTYEAR% +## Check domain restrictions #3 +COOKIE ASK http://novell12.com/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +## Check domain restrictions #4 +COOKIE ASK http://novell13.com/ Set-Cookie: some_value=value1; Domain=".com"; expires=%NEXTYEAR% +# If the specified domain is too broad, we default to host only +## Check domain restrictions #5 +COOKIE ASK http://novell14.co.uk/ Set-Cookie: some_value=value1; Domain=".co.uk"; expires=%NEXTYEAR% +COOKIE ASK http://x.y.z.foobar14.com/ Set-Cookie: set_by=x.y.z.foobar14.com; Domain=".foobar14.com"; expires=%NEXTYEAR% +## Check domain restrictions #6 +COOKIE ASK http://x.y.z.frop15.com/ Set-Cookie: set_by=x.y.z.frop15.com; Domain=".foobar15.com"; expires=%NEXTYEAR% +COOKIE ASK http://x.y.z.frop15.com/ Set-Cookie: set_by2=x.y.z.frop15.com; Domain=".com"; expires=%NEXTYEAR% +## Check domain restrictions #7 +COOKIE ASK http://frop16.com/ Set-Cookie: set_by=x.y.z.frop16.com; Domain=".foobar16.com"; expires=%NEXTYEAR% +COOKIE ASK http://frop16.com/ Set-Cookie: set_by2=x.y.z.frop16.com; Domain=".com"; expires=%NEXTYEAR% +## RFC Cookies +## Check setting of cookies +COOKIE ASK http://w.y20.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600 +# Although the examples in RFC2965 uses $Version="1" the syntax description suggests that +# such quotes are not allowed, KDE BR59990 reports that the Sun Java server fails to handle +# cookies that use $Version="1" +COOKIE ASK http://a.b20.c/ Set-Cookie2: some_value="value2"; Version=1; Path="/"; Max-Age=3600 +## Check cookie syntax +COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value="value with spaces"; Version=1; Max-Age=3600 +COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value ="extra space 1"; Version=1; Max-Age=3600 +COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value= "extra space 2"; Version=1; Max-Age=3600 +COOKIE ASK http://a.b21.c/ Set-Cookie2: some_value=unquoted; Version=1; Max-Age=3600 +# Note that we parse this different for Netscape-style cookies! +COOKIE ASK http://a.b21.c/ Set-Cookie2: some_value="quoted value; and such"; Version=1; Max-Age=3600 +## Check if deleting cookies works #1 +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600 +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value1; Version=1; Path="/"; Max-Age=0 +## Check if updating cookies works +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Max-Age=3600 +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600 +## Check if multiple cookies work +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value2=foobar; Version=1; Path="/"; Max-Age=3600 +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0 +## Check if path restrictions work +COOKIE ASK http://w.y23.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600 +## Check if default path works +# RFC2965 says that we should default to the URL path +COOKIE ASK http://w.y24.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +## Check if cookies are correctly ordered based on path +COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600 +COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/Foo/Bar"; Max-Age=3600 +COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600 +## Check cookies with same name but different paths +COOKIE ASK http://w.y26.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +COOKIE ASK http://w.y26.z/Bar/ Set-Cookie2: some_value=value2; Version=1; Max-Age=3600 +COOKIE ASK http://w.y26.z/ Set-Cookie2: some_value=value3; Version=1; Max-Age=3600 +## Check secure cookie handling +COOKIE ASK https://secure.y26.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/"; Max-Age=3600; Secure +COOKIE ASK http://secure.y27.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600; Secure +## Check domain restrictions #1 +COOKIE ASK http://www.acme28.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".acme28.com"; Max-Age=3600 +## Check domain restrictions #2 +COOKIE ASK http://novell29.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".novell29.com"; Max-Age=3600 +## Check domain restrictions #3 +COOKIE ASK http://novell30.com/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +## Check domain restrictions #4 +COOKIE ASK http://novell31.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".com"; Max-Age=3600 +# If the specified domain is too broad, we ignore the Domain +# FIXME: RFC2965 says we should ignore the cookie completely +## Check domain restrictions #5 +COOKIE ASK http://novell32.co.uk/ Set-Cookie2: some_value=value1; Version=1; Domain=".co.uk"; Max-Age=3600 +# If the specified domain is too broad, we default to host only +# FIXME: RFC2965 says we should ignore the cookie completely +COOKIE ASK http://x.y.z.foobar33.com/ Set-Cookie2: set_by=x.y.z.foobar.com; Version=1; Domain=".foobar33.com"; Max-Age=3600 +## Check domain restrictions #6 +COOKIE ASK http://x.y.z.frop34.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +COOKIE ASK http://x.y.z.frop34.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600 +## Check domain restrictions #7 +COOKIE ASK http://frop35.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +COOKIE ASK http://frop35.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600 +## Check port restrictions (RFC2965 3.3.4) +COOKIE ASK http://ports.foo.bar.com Set-Cookie2: name=value1; Version=1; Port="80 8080 443"; Max-Age=3600 + +## Check results +CHECK http://w.y.z/ +CHECK http://a.b.c/ +CHECK http://w.y1.z/ Cookie: some_value=value with spaces +CHECK http://a.b1.c/ Cookie: some_value="quoted value"; some_other_value +CHECK http://a.b2.c/ Cookie: some_value="quoted value +CHECK http://w.y3.z/ Cookie: some_value2=foobar +CHECK http://w.y4.z/ +CHECK http://w.y4.z/Foo Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y5.z/ +CHECK http://w.y5.z/Foo Cookie: some_value=value1 +CHECK http://w.y5.z/FooBar +CHECK http://w.y5.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3 +CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3 +CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3 +CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2 +CHECK http://secure.y7.z/Foo/bar +CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3 +CHECK http://secure.y8.z/Foo/bar +CHECK http://www.acme9.com/ Cookie: some_value=value1 +CHECK http://www.abc9.com/ +CHECK http://frop.acme9.com/ Cookie: some_value=value1 +CHECK http://novell10.com/ Cookie: some_value=value1 +CHECK http://www.novell10.com/ Cookie: some_value=value1 +CHECK http://novell11.com/ Cookie: some_value=value1 +CHECK http://www.novell11.com/ Cookie: some_value=value1 +CHECK http://novell12.com/ Cookie: some_value=value1 +CHECK http://www.novell12.com/ +CHECK http://novell13.com/ Cookie: some_value=value1 +CHECK http://www.novell13.com/ +CHECK http://com/ +CHECK http://sun13.com/ +CHECK http://novell14.co.uk/ Cookie: some_value=value1 +CHECK http://www.novell14.co.uk/ +CHECK http://co.uk/ +CHECK http://sun14.co.uk/ +CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://x.y.z.foobar15.com/ +CHECK http://y.z.foobar15.com/ +CHECK http://z.foobar15.com/ +CHECK http://www.foobar15.com/ +CHECK http://foobar15.com/ +CHECK http://x.y.z.foobar16.com/ +CHECK http://y.z.foobar16.com/ +CHECK http://z.foobar16.com/ +CHECK http://www.foobar16.com/ +CHECK http://foobar16.com/ +## Check results for RFC cookies +CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/" +CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2" +CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such" +CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CHECK http://w.y23.z/ +CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y24.z/ +CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/FooBar +CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1 +CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/" +CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3 +CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3 +CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/" +CHECK http://secure.y26.z/Foo/bar +CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/" +CHECK http://secure.y27.z/Foo/bar +CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://www.abc28.com/ +CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell30.com/ +CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell31.com/ +CHECK http://com/ +CHECK http://sun31.com/ +CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell32.co.uk/ +CHECK http://co.uk/ +CHECK http://sun32.co.uk/ +CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CHECK http://ports.foo.bar.com Cookie: $Version=1; name=value1; $Port="80 8080 443" + +SAVE +## Check result after saving +CHECK http://w.y.z/ +CHECK http://a.b.c/ +CHECK http://w.y1.z/ Cookie: some_value=value with spaces +CHECK http://a.b1.c/ Cookie: some_value="quoted value"; some_other_value +CHECK http://a.b2.c/ Cookie: some_value="quoted value +CHECK http://w.y3.z/ Cookie: some_value2=foobar +CHECK http://w.y4.z/ +CHECK http://w.y4.z/Foo Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y5.z/ +CHECK http://w.y5.z/Foo Cookie: some_value=value1 +CHECK http://w.y5.z/FooBar +CHECK http://w.y5.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3 +CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3 +CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3 +CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2 +CHECK http://secure.y7.z/Foo/bar +CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3 +CHECK http://secure.y8.z/Foo/bar +CHECK http://www.acme9.com/ Cookie: some_value=value1 +CHECK http://www.abc9.com/ +CHECK http://frop.acme9.com/ Cookie: some_value=value1 +CHECK http://novell10.com/ Cookie: some_value=value1 +CHECK http://www.novell10.com/ Cookie: some_value=value1 +CHECK http://novell11.com/ Cookie: some_value=value1 +CHECK http://www.novell11.com/ Cookie: some_value=value1 +CHECK http://novell12.com/ Cookie: some_value=value1 +CHECK http://www.novell12.com/ +CHECK http://novell13.com/ Cookie: some_value=value1 +CHECK http://www.novell13.com/ +CHECK http://com/ +CHECK http://sun13.com/ +CHECK http://novell14.co.uk/ Cookie: some_value=value1 +CHECK http://www.novell14.co.uk/ +CHECK http://co.uk/ +CHECK http://sun14.co.uk/ +CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://x.y.z.foobar15.com/ +CHECK http://y.z.foobar15.com/ +CHECK http://z.foobar15.com/ +CHECK http://www.foobar15.com/ +CHECK http://foobar15.com/ +CHECK http://x.y.z.foobar16.com/ +CHECK http://y.z.foobar16.com/ +CHECK http://z.foobar16.com/ +CHECK http://www.foobar16.com/ +CHECK http://foobar16.com/ +## Check result for RFC cookies after saving +CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/" +CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2" +CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such" +CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CHECK http://w.y23.z/ +CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y24.z/ +CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/FooBar +CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1 +CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/" +CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3 +CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3 +CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/" +CHECK http://secure.y26.z/Foo/bar +CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/" +CHECK http://secure.y27.z/Foo/bar +CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://www.abc28.com/ +CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell30.com/ +CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell31.com/ +CHECK http://com/ +CHECK http://sun31.com/ +CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell32.co.uk/ +CHECK http://co.uk/ +CHECK http://sun32.co.uk/ +CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CHECK http://ports.foo.bar.com Cookie: $Version=1; name=value1; $Port="80 8080 443" + +SAVE +## Check result after saving a second time +CHECK http://w.y.z/ +CHECK http://a.b.c/ +CHECK http://w.y1.z/ Cookie: some_value=value with spaces +CHECK http://a.b1.c/ Cookie: some_value="quoted value"; some_other_value +CHECK http://a.b2.c/ Cookie: some_value="quoted value +CHECK http://w.y3.z/ Cookie: some_value2=foobar +CHECK http://w.y4.z/ +CHECK http://w.y4.z/Foo Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y5.z/ +CHECK http://w.y5.z/Foo Cookie: some_value=value1 +CHECK http://w.y5.z/FooBar +CHECK http://w.y5.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3 +CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3 +CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3 +CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2 +CHECK http://secure.y7.z/Foo/bar +CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3 +CHECK http://secure.y8.z/Foo/bar +CHECK http://www.acme9.com/ Cookie: some_value=value1 +CHECK http://www.abc9.com/ +CHECK http://frop.acme9.com/ Cookie: some_value=value1 +CHECK http://novell10.com/ Cookie: some_value=value1 +CHECK http://www.novell10.com/ Cookie: some_value=value1 +CHECK http://novell11.com/ Cookie: some_value=value1 +CHECK http://www.novell11.com/ Cookie: some_value=value1 +CHECK http://novell12.com/ Cookie: some_value=value1 +CHECK http://www.novell12.com/ +CHECK http://novell13.com/ Cookie: some_value=value1 +CHECK http://www.novell13.com/ +CHECK http://com/ +CHECK http://sun13.com/ +CHECK http://novell14.co.uk/ Cookie: some_value=value1 +CHECK http://www.novell14.co.uk/ +CHECK http://co.uk/ +CHECK http://sun14.co.uk/ +CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://x.y.z.foobar15.com/ +CHECK http://y.z.foobar15.com/ +CHECK http://z.foobar15.com/ +CHECK http://www.foobar15.com/ +CHECK http://foobar15.com/ +CHECK http://x.y.z.foobar16.com/ +CHECK http://y.z.foobar16.com/ +CHECK http://z.foobar16.com/ +CHECK http://www.foobar16.com/ +CHECK http://foobar16.com/ +## Check result for rfc cookies after saving a second time +CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/" +CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2" +CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such" +CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CHECK http://w.y23.z/ +CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y24.z/ +CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/FooBar +CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1 +CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/" +CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3 +CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3 +CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/" +CHECK http://secure.y26.z/Foo/bar +CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/" +CHECK http://secure.y27.z/Foo/bar +CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://www.abc28.com/ +CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell30.com/ +CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell31.com/ +CHECK http://com/ +CHECK http://sun31.com/ +CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell32.co.uk/ +CHECK http://co.uk/ +CHECK http://sun32.co.uk/ +CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CHECK http://ports.foo.bar.com Cookie: $Version=1; name=value1; $Port="80 8080 443" diff --git a/autotests/kcookiejar/cookie_session.test b/autotests/kcookiejar/cookie_session.test new file mode 100644 index 0000000..2122660 --- /dev/null +++ b/autotests/kcookiejar/cookie_session.test @@ -0,0 +1,51 @@ +## Check that persistent cookies are not deleted at the end of the session +CLEAR CONFIG +CONFIG CookieGlobalAdvice Accept +COOKIE ACCEPT http://a.example1.net/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.example2.net/ Set-Cookie: some_value=value2; Path="/"; max-age="600" +CHECK http://a.example1.net/ Cookie: some_value=value1 +CHECK http://a.example2.net/ Cookie: some_value=value2 +ENDSESSION +CHECK http://a.example1.net/ Cookie: some_value=value1 +CHECK http://a.example2.net/ Cookie: some_value=value2 +CONFIG CookieGlobalAdvice Reject +CONFIG CookieDomainAdvice a.example3.net:Accept,.example4.net:Accept +COOKIE ACCEPT http://a.example3.net/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.example4.net/ Set-Cookie: some_value=value4; Path="/"; expires=%NEXTYEAR% +CHECK http://a.example3.net/ Cookie: some_value=value3 +CHECK http://a.example4.net/ Cookie: some_value=value4 +ENDSESSION +CHECK http://a.example3.net/ Cookie: some_value=value3 +CHECK http://a.example4.net/ Cookie: some_value=value4 +## Check that non persistent cookies are deleted at the end of the session +CLEAR CONFIG +CONFIG CookieGlobalAdvice Accept +COOKIE ACCEPT http://x.example1.net/ Set-Cookie: some_value=value1; Path="/" +CHECK http://x.example1.net/ Cookie: some_value=value1 +ENDSESSION +CHECK http://x.example1.net/ +CONFIG CookieGlobalAdvice AcceptForSession +COOKIE ACCEPTFORSESSION http://x.example2.net/ Set-Cookie: some_value=value2; Path="/" +COOKIE ACCEPTFORSESSION http://x.example3.net/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR% +CHECK http://x.example2.net/ Cookie: some_value=value2 +CHECK http://x.example3.net/ Cookie: some_value=value3 +ENDSESSION +CHECK http://x.example2.net/ +CHECK http://x.example3.net/ +CONFIG CookieGlobalAdvice Reject +CONFIG CookieDomainAdvice x.example4.net:AcceptForSession,.example5.net:AcceptForSession,x.y.example6.net:AcceptForSession,.y.example6.net:Accept +COOKIE ACCEPTFORSESSION http://x.example4.net/ Set-Cookie: some_value=value4; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPTFORSESSION http://x.example5.net/ Set-Cookie: some_value=value5; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPTFORSESSION http://x.y.example6.net/ Set-Cookie: some_value=value6; Path="/"; expires=%NEXTYEAR% +CHECK http://x.example4.net/ Cookie: some_value=value4 +CHECK http://x.example5.net/ Cookie: some_value=value5 +CHECK http://x.y.example6.net/ Cookie: some_value=value6 +ENDSESSION +CHECK http://x.example4.net/ +CHECK http://x.example5.net/ +CHECK http://x.y.example6.net/ +CONFIG AcceptSessionCookies true +COOKIE ACCEPT http://x.example7.net/ Set-Cookie: some_value=value7; Path="/" +CHECK http://x.example7.net/ Cookie: some_value=value7 +ENDSESSION +CHECK http://x.example7.net/ diff --git a/autotests/kcookiejar/cookie_settings.test b/autotests/kcookiejar/cookie_settings.test new file mode 100644 index 0000000..8a014cf --- /dev/null +++ b/autotests/kcookiejar/cookie_settings.test @@ -0,0 +1,112 @@ +## Check CookieGlobalAdvice setting +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/" +CONFIG CookieGlobalAdvice Reject +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value4; Path="/" +CONFIG CookieGlobalAdvice Accept +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value5; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value6; Path="/" +CONFIG CookieGlobalAdvice AcceptForSession +COOKIE ACCEPTFORSESSION http://a.b.c/ Set-Cookie: some_value=value5; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPTFORSESSION http://a.b.c/ Set-Cookie: some_value=value6; Path="/" +CONFIG CookieGlobalAdvice Ask +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value7; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value8; Path="/" +CONFIG AcceptSessionCookies true +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +# FIXME: Shouldn't this be considered a session cookie? +# COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="0" +# COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%LASTYEAR% +# FIXME: The 'Discard' attribute makes the cookie a session cookie +# COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +## Check host-based domain policies +CONFIG AcceptSessionCookies false +CONFIG CookieDomainAdvice a.b.c:Reject +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check resetting of domain policies +CONFIG CookieDomainAdvice +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check domain policies +CONFIG CookieDomainAdvice .b.c:Reject +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check overriding of domain policies #1 +CONFIG CookieDomainAdvice .b.c:Reject,a.b.c:Accept +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check overriding of domain policies #2 +CONFIG CookieDomainAdvice a.b.c:Reject,.b.c:Accept +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check resetting of domain policies +CONFIG CookieDomainAdvice +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check overriding of domain policies #3 +CONFIG CookieDomainAdvice b.c:Reject,.b.c:Accept +COOKIE REJECT http://b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check overriding of domain policies #4 +CONFIG CookieDomainAdvice .a.b.c.d:Reject,.b.c.d:Accept,.c.d:Ask +COOKIE REJECT http://www.a.b.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://www.b.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://www.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +## Check interaction with session policy +CONFIG AcceptSessionCookies true +CONFIG CookieDomainAdvice .b.c:Reject +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" diff --git a/autotests/kcookiejar/kcookiejartest.cpp b/autotests/kcookiejar/kcookiejartest.cpp new file mode 100644 index 0000000..30ce6ea --- /dev/null +++ b/autotests/kcookiejar/kcookiejartest.cpp @@ -0,0 +1,320 @@ +/* + This file is part of KDE + + Copyright (C) 2004 Waldo Bastian (bastian@kde.org) + Copyright 2008 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License + as published by the Free Software Foundation; either + version 2, or (at your option) version 3. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include + +#include +#include +#include + +#include "../../src/ioslaves/http/kcookiejar/kcookiejar.cpp" + +static KCookieJar *jar; +static QString *lastYear; +static QString *nextYear; +static KConfig *config = 0; +static int windowId = 1234; // random number to be used as windowId for test cookies + +static void FAIL(const QString &msg) +{ + qWarning("%s", msg.toLocal8Bit().data()); + exit(1); +} + +static void popArg(QString &command, QString &line) +{ + int i = line.indexOf(' '); + if (i != -1) { + command = line.left(i); + line = line.mid(i + 1); + } else { + command = line; + line.clear(); + } +} + +static void clearConfig() +{ + delete config; + QString file = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + "kcookiejar-testconfig"; + QFile::remove(file); + config = new KConfig(file); + KConfigGroup cg(config, "Cookie Policy"); + cg.writeEntry("RejectCrossDomainCookies", false); + cg.writeEntry("AcceptSessionCookies", false); + cg.writeEntry("CookieGlobalAdvice", "Ask"); + jar->loadConfig(config, false); +} + +static void clearCookies(bool sessionOnly = false) +{ + if (sessionOnly) { + jar->eatSessionCookies(windowId); + } else { + jar->eatAllCookies(); + } +} + +static void saveCookies() +{ + QString file = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + "kcookiejar-testcookies"; + QFile::remove(file); + jar->saveCookies(file); + + // Add an empty domain to the cookies file, just for testing robustness + QFile f(file); + f.open(QIODevice::Append); + f.write("[]\n \"\" \"/\" 1584320400 0 h 4 x\n"); + f.close(); + + delete jar; + jar = new KCookieJar(); + clearConfig(); + jar->loadCookies(file); +} + +static void endSession() +{ + jar->eatSessionCookies(windowId); +} + +static void processCookie(QString &line) +{ + QString policy; + popArg(policy, line); + KCookieAdvice expectedAdvice = KCookieJar::strToAdvice(policy); + if (expectedAdvice == KCookieDunno) { + FAIL(QStringLiteral("Unknown accept policy '%1'").arg(policy)); + } + + QString urlStr; + popArg(urlStr, line); + QUrl url(urlStr); + if (!url.isValid()) { + FAIL(QStringLiteral("Invalid URL '%1'").arg(urlStr)); + } + if (url.isEmpty()) { + FAIL(QStringLiteral("Missing URL")); + } + + line.replace(QLatin1String("%LASTYEAR%"), *lastYear); + line.replace(QLatin1String("%NEXTYEAR%"), *nextYear); + + KHttpCookieList list = jar->makeCookies(urlStr, line.toUtf8(), windowId); + + if (list.isEmpty()) { + FAIL(QStringLiteral("Failed to make cookies from: '%1'").arg(line)); + } + + for (KHttpCookieList::iterator cookieIterator = list.begin(); + cookieIterator != list.end(); ++cookieIterator) { + KHttpCookie &cookie = *cookieIterator; + const KCookieAdvice cookieAdvice = jar->cookieAdvice(cookie); + if (cookieAdvice != expectedAdvice) + FAIL(urlStr + QStringLiteral("\n'%2'\nGot advice '%3' expected '%4'") + .arg(line, KCookieJar::adviceToStr(cookieAdvice), KCookieJar::adviceToStr(expectedAdvice)) + ); + jar->addCookie(cookie); + } +} + +static void processCheck(QString &line) +{ + QString urlStr; + popArg(urlStr, line); + QUrl url(urlStr); + if (!url.isValid()) { + FAIL(QStringLiteral("Invalid URL '%1'").arg(urlStr)); + } + if (url.isEmpty()) { + FAIL(QStringLiteral("Missing URL")); + } + + QString expectedCookies = line; + + QString cookies = jar->findCookies(urlStr, false, windowId, 0).trimmed(); + if (cookies != expectedCookies) + FAIL(urlStr + QStringLiteral("\nGot '%1' expected '%2'") + .arg(cookies, expectedCookies)); +} + +static void processClear(QString &line) +{ + if (line == QLatin1String("CONFIG")) { + clearConfig(); + } else if (line == QLatin1String("COOKIES")) { + clearCookies(); + } else if (line == QLatin1String("SESSIONCOOKIES")) { + clearCookies(true); + } else { + FAIL(QStringLiteral("Unknown command 'CLEAR %1'").arg(line)); + } +} + +static void processConfig(QString &line) +{ + QString key; + popArg(key, line); + + if (key.isEmpty()) { + FAIL(QStringLiteral("Missing Key")); + } + + KConfigGroup cg(config, "Cookie Policy"); + cg.writeEntry(key, line); + jar->loadConfig(config, false); +} + +static void processLine(QString line) +{ + if (line.isEmpty()) { + return; + } + + if (line[0] == '#') { + if (line[1] == '#') { + qDebug("%s", line.toLatin1().constData()); + } + return; + } + + QString command; + popArg(command, line); + if (command.isEmpty()) { + return; + } + + if (command == QLatin1String("COOKIE")) { + processCookie(line); + } else if (command == QLatin1String("CHECK")) { + processCheck(line); + } else if (command == QLatin1String("CLEAR")) { + processClear(line); + } else if (command == QLatin1String("CONFIG")) { + processConfig(line); + } else if (command == QLatin1String("SAVE")) { + saveCookies(); + } else if (command == QLatin1String("ENDSESSION")) { + endSession(); + } else { + FAIL(QStringLiteral("Unknown command '%1'").arg(command)); + } +} + +static void runRegression(const QString &filename) +{ + FILE *file = QT_FOPEN(QFile::encodeName(filename).constData(), "r"); + if (!file) { + FAIL(QStringLiteral("Can't open '%1'").arg(filename)); + } + + char buf[4096]; + while (fgets(buf, sizeof(buf), file)) { + int l = strlen(buf); + if (l) { + l--; + buf[l] = 0; + } + processLine(QString::fromUtf8(buf)); + } + fclose(file); + qDebug("%s OK", filename.toLocal8Bit().data()); +} + +class KCookieJarTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase() + { + QStandardPaths::enableTestMode(true); + jar = new KCookieJar; + QDateTime dt = QDateTime::currentDateTime(); + lastYear = new QString(dt.addYears(-1).toString(Qt::RFC2822Date)); + nextYear = new QString(dt.addYears(1).toString(Qt::RFC2822Date)); + } + void testCookieFile_data() + { + QTest::addColumn("fileName"); + QTest::newRow("cookie.test") << QFINDTESTDATA("cookie.test"); + QTest::newRow("cookie_rfc.test") << QFINDTESTDATA("cookie_rfc.test"); + QTest::newRow("cookie_saving.test") << QFINDTESTDATA("cookie_saving.test"); + QTest::newRow("cookie_settings.test") << QFINDTESTDATA("cookie_settings.test"); + QTest::newRow("cookie_session.test") << QFINDTESTDATA("cookie_session.test"); + } + void testCookieFile() + { + QFETCH(QString, fileName); + clearConfig(); + runRegression(fileName); + } + + void testParseUrl_data() + { + QTest::addColumn("url"); + QTest::addColumn("expectedResult"); + QTest::addColumn("expectedFqdn"); + QTest::addColumn("expectedPath"); + QTest::newRow("empty") << "" << false << "" << ""; + QTest::newRow("url with no path") << "http://bugs.kde.org" << true << "bugs.kde.org" << "/"; + QTest::newRow("url with path") << "http://bugs.kde.org/foo" << true << "bugs.kde.org" << "/foo"; + QTest::newRow("just a host") << "bugs.kde.org" << false << "" << ""; + } + void testParseUrl() + { + QFETCH(QString, url); + QFETCH(bool, expectedResult); + QFETCH(QString, expectedFqdn); + QFETCH(QString, expectedPath); + QString fqdn; + QString path; + bool result = KCookieJar::parseUrl(url, fqdn, path); + QCOMPARE(result, expectedResult); + QCOMPARE(fqdn, expectedFqdn); + QCOMPARE(path, expectedPath); + } + + void testExtractDomains_data() + { + QTest::addColumn("fqdn"); + QTest::addColumn("expectedDomains"); + QTest::newRow("empty") << "" << (QStringList() << QStringLiteral("localhost")); + QTest::newRow("ipv4") << "1.2.3.4" << (QStringList() << QStringLiteral("1.2.3.4")); + QTest::newRow("ipv6") << "[fe80::213:d3ff:fef4:8c92]" << (QStringList() << QStringLiteral("[fe80::213:d3ff:fef4:8c92]")); + QTest::newRow("bugs.kde.org") << "bugs.kde.org" << (QStringList() << QStringLiteral("bugs.kde.org") << QStringLiteral(".bugs.kde.org") << QStringLiteral("kde.org") << QStringLiteral(".kde.org")); + + } + void testExtractDomains() + { + QFETCH(QString, fqdn); + QFETCH(QStringList, expectedDomains); + KCookieJar jar; + QStringList lst; + jar.extractDomains(fqdn, lst); + QCOMPARE(lst, expectedDomains); + } +}; + +QTEST_MAIN(KCookieJarTest) + +#include "kcookiejartest.moc" diff --git a/autotests/kdirlistertest.cpp b/autotests/kdirlistertest.cpp new file mode 100644 index 0000000..a91988f --- /dev/null +++ b/autotests/kdirlistertest.cpp @@ -0,0 +1,1387 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kdirlistertest.h" +#include +#include +#include + +QTEST_MAIN(KDirListerTest) + +#include +#include "kiotesthelper.h" +#include +#include +#include +#include +#include + +#define WORKAROUND_BROKEN_INOTIFY 0 + +void MyDirLister::handleError(KIO::Job *job) +{ + // Currently we don't expect any errors. + qCritical() << "KDirLister called handleError!" << job << job->error() << job->errorString(); + qFatal("aborting"); +} + +void KDirListerTest::initTestCase() +{ + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + + KIO::setDefaultJobUiDelegateExtension(0); // no "skip" dialogs + + m_exitCount = 1; + + s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-120); // 2 minutes ago + + // Create test data: + /* + * PATH/toplevelfile_1 + * PATH/toplevelfile_2 + * PATH/toplevelfile_3 + * PATH/subdir + * PATH/subdir/testfile + * PATH/subdir/subsubdir + * PATH/subdir/subsubdir/testfile + */ + const QString path = m_tempDir.path() + '/'; + createTestFile(path + "toplevelfile_1"); + createTestFile(path + "toplevelfile_2"); + createTestFile(path + "toplevelfile_3"); + createTestDirectory(path + "subdir"); + createTestDirectory(path + "subdir/subsubdir"); + + qRegisterMetaType > >(); +} + +void KDirListerTest::cleanup() +{ + m_dirLister.clearSpies(); + disconnect(&m_dirLister, 0, this, 0); +} + +void KDirListerTest::testOpenUrl() +{ + m_items.clear(); + const QString path = m_tempDir.path() + '/'; + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + // The call to openUrl itself, emits started + m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + + QCOMPARE(m_dirLister.spyStarted.count(), 1); + QCOMPARE(m_dirLister.spyCompleted.count(), 0); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_dirLister.spyRedirection.count(), 0); + QCOMPARE(m_items.count(), 0); + QVERIFY(!m_dirLister.isFinished()); + + // then wait for completed + qDebug("waiting for completed"); + connect(&m_dirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + QCOMPARE(m_dirLister.spyStarted.count(), 1); + QCOMPARE(m_dirLister.spyCompleted.count(), 1); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_dirLister.spyRedirection.count(), 0); + //qDebug() << m_items; + //qDebug() << "In dir" << QDir(path).entryList( QDir::AllEntries | QDir::NoDotAndDotDot); + QCOMPARE(m_items.count(), fileCount()); + QVERIFY(m_dirLister.isFinished()); + disconnect(&m_dirLister, 0, this, 0); + + const QString fileName = QStringLiteral("toplevelfile_3"); + const QUrl itemUrl = QUrl::fromLocalFile(path + fileName); + KFileItem byName = m_dirLister.findByName(fileName); + QVERIFY(!byName.isNull()); + QCOMPARE(byName.url().toString(), itemUrl.toString()); + QCOMPARE(byName.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); + KFileItem byUrl = m_dirLister.findByUrl(itemUrl); + QVERIFY(!byUrl.isNull()); + QCOMPARE(byUrl.url().toString(), itemUrl.toString()); + QCOMPARE(byUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); + KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl); + QVERIFY(!itemForUrl.isNull()); + QCOMPARE(itemForUrl.url().toString(), itemUrl.toString()); + QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); + + KFileItem rootByUrl = m_dirLister.findByUrl(QUrl::fromLocalFile(path)); + QVERIFY(!rootByUrl.isNull()); + QCOMPARE(QString(rootByUrl.url().toLocalFile() + '/'), path); + + m_dirLister.clearSpies(); // for the tests that call testOpenUrl for setup +} + +// This test assumes testOpenUrl was run before. So m_dirLister is holding the items already. +void KDirListerTest::testOpenUrlFromCache() +{ + // Do the same again, it should behave the same, even with the items in the cache + testOpenUrl(); + + // Get into the case where another dirlister is holding the items + { + m_items.clear(); + const QString path = m_tempDir.path() + '/'; + MyDirLister secondDirLister; + connect(&secondDirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + QCOMPARE(secondDirLister.spyStarted.count(), 1); + QCOMPARE(secondDirLister.spyCompleted.count(), 0); + QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(secondDirLister.spyCanceled.count(), 0); + QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(secondDirLister.spyClear.count(), 1); + QCOMPARE(secondDirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_items.count(), 0); + QVERIFY(!secondDirLister.isFinished()); + + // then wait for completed + qDebug("waiting for completed"); + connect(&secondDirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + QCOMPARE(secondDirLister.spyStarted.count(), 1); + QCOMPARE(secondDirLister.spyCompleted.count(), 1); + QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(secondDirLister.spyCanceled.count(), 0); + QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(secondDirLister.spyClear.count(), 1); + QCOMPARE(secondDirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_items.count(), 4); + QVERIFY(secondDirLister.isFinished()); + } + + disconnect(&m_dirLister, 0, this, 0); +} + +// This test assumes testOpenUrl was run before. So m_dirLister is holding the items already. +void KDirListerTest::testNewItems() +{ + QCOMPARE(m_items.count(), 4); + const QString path = m_tempDir.path() + '/'; + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + + QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything. + + qDebug() << "Creating new file"; + const QString fileName = QStringLiteral("toplevelfile_new"); + createSimpleFile(path + fileName); + + int numTries = 0; + // Give time for KDirWatch to notify us + while (m_items.count() == 4) { + QVERIFY(++numTries < 20); + QTest::qWait(100); + } + //qDebug() << "numTries=" << numTries; + QCOMPARE(m_items.count(), 5); + + QCOMPARE(m_dirLister.spyStarted.count(), 1); // Updates call started + QCOMPARE(m_dirLister.spyCompleted.count(), 1); // and completed + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 0); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + + const QUrl itemUrl = QUrl::fromLocalFile(path + fileName); + KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl); + QVERIFY(!itemForUrl.isNull()); + QCOMPARE(itemForUrl.url().toString(), itemUrl.toString()); + QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); +} + +void KDirListerTest::testNewItemByCopy() +{ + // This test creates a file using KIO::copyAs, like knewmenu.cpp does. + // Useful for testing #192185, i.e. whether we catch the kdirwatch event and avoid + // a KFileItem::refresh(). + const int origItemCount = m_items.count(); + const QString path = m_tempDir.path() + '/'; + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + + QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything. + + const QString fileName = QStringLiteral("toplevelfile_copy"); + const QUrl itemUrl = QUrl::fromLocalFile(path + fileName); + KIO::CopyJob *job = KIO::copyAs(QUrl::fromLocalFile(path + "toplevelfile_3"), itemUrl, KIO::HideProgressInfo); + job->exec(); + + int numTries = 0; + // Give time for KDirWatch/KDirNotify to notify us + while (m_items.count() == origItemCount) { + QVERIFY(++numTries < 10); + QTest::qWait(200); + } + //qDebug() << "numTries=" << numTries; + QCOMPARE(m_items.count(), origItemCount + 1); + + QCOMPARE(m_dirLister.spyStarted.count(), 1); // Updates call started + QCOMPARE(m_dirLister.spyCompleted.count(), 1); // and completed + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 0); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + + // Give some time to KDirWatch + QTest::qWait(1000); + + KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl); + QVERIFY(!itemForUrl.isNull()); + QCOMPARE(itemForUrl.url().toString(), itemUrl.toString()); + QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); +} + +void KDirListerTest::testNewItemsInSymlink() // #213799 +{ + const int origItemCount = m_items.count(); + QCOMPARE(fileCount(), origItemCount); + const QString path = m_tempDir.path() + '/'; + QTemporaryFile tempFile; + QVERIFY(tempFile.open()); + const QString symPath = tempFile.fileName() + "_link"; + tempFile.close(); + const bool symlinkOk = KIOPrivate::createSymlink(path, symPath); + if (!symlinkOk) { + const QString error = QString::fromLatin1("Failed to create symlink '%1' pointing to '%2': %3") + .arg(symPath, path, QString::fromLocal8Bit(strerror(errno))); + QVERIFY2(symlinkOk, qPrintable(error)); + } + MyDirLister dirLister2; + m_items2.clear(); + connect(&dirLister2, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems2(KFileItemList))); + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + + // The initial listing + dirLister2.openUrl(QUrl::fromLocalFile(symPath), KDirLister::NoFlags); + connect(&dirLister2, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + QCOMPARE(m_items2.count(), origItemCount); + QVERIFY(dirLister2.isFinished()); + + QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything. + + qDebug() << "Creating new file"; + const QString fileName = QStringLiteral("toplevelfile_newinlink"); + createSimpleFile(path + fileName); + +#if WORKAROUND_BROKEN_INOTIFY + org::kde::KDirNotify::emitFilesAdded(path); +#endif + // Give time for KDirWatch to notify us + QTRY_COMPARE(m_items2.count(), origItemCount + 1); + QTRY_COMPARE(m_items.count(), origItemCount + 1); + + // Now create an item using the symlink-path + const QString fileName2 = QStringLiteral("toplevelfile_newinlink2"); + { + createSimpleFile(path + fileName2); + + int numTries = 0; + // Give time for KDirWatch to notify us + while (m_items2.count() == origItemCount + 1) { + QVERIFY(++numTries < 10); + QTest::qWait(200); + } + QCOMPARE(m_items2.count(), origItemCount + 2); + QCOMPARE(m_items.count(), origItemCount + 2); + } + QCOMPARE(fileCount(), m_items.count()); + + // Test file deletion + { + qDebug() << "Deleting" << (path + fileName); + QTest::qWait(1000); // for timestamp difference + QFile::remove(path + fileName); + int numTries = 0; + while (dirLister2.spyItemsDeleted.count() == 0) { + QVERIFY(++numTries < 10); + QTest::qWait(200); + } + QCOMPARE(dirLister2.spyItemsDeleted.count(), 1); + const KFileItem item = dirLister2.spyItemsDeleted[0][0].value().at(0); + QCOMPARE(item.url().toLocalFile(), QString(symPath + '/' + fileName)); + } + + // TODO: test file update. + disconnect(&m_dirLister, 0, this, 0); + + QFile::remove(symPath); +} + +// This test assumes testOpenUrl was run before. So m_dirLister is holding the items already. +void KDirListerTest::testRefreshItems() +{ + m_refreshedItems.clear(); + + const QString path = m_tempDir.path() + '/'; + const QString fileName = path + "toplevelfile_1"; + KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(fileName)); + QVERIFY(!cachedItem.isNull()); + QCOMPARE(cachedItem.mimetype(), QString("application/octet-stream")); + + connect(&m_dirLister, SIGNAL(refreshItems(QList >)), + this, SLOT(slotRefreshItems(QList >))); + + QFile file(fileName); + QVERIFY(file.open(QIODevice::Append)); + file.write(QByteArray("")); + file.close(); + QCOMPARE(QFileInfo(fileName).size(), 11LL /*Hello world*/ + 6 /**/); + + QVERIFY(waitForRefreshedItems()); + + QCOMPARE(m_dirLister.spyStarted.count(), 0); // fast path: no directory listing needed + QCOMPARE(m_dirLister.spyCompleted.count(), 0); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 0); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_refreshedItems.count(), 1); + QPair entry = m_refreshedItems.first(); + QCOMPARE(entry.first.url().toLocalFile(), fileName); + QCOMPARE(entry.first.size(), KIO::filesize_t(11)); + QCOMPARE(entry.first.mimetype(), QString("application/octet-stream")); + QCOMPARE(entry.second.url().toLocalFile(), fileName); + QCOMPARE(entry.second.size(), KIO::filesize_t(11 /*Hello world*/ + 6 /**/)); + QCOMPARE(entry.second.mimetype(), QString("text/html")); + + // Let's see what KDirLister has in cache now + cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(fileName)); + QCOMPARE(cachedItem.size(), KIO::filesize_t(11 /*Hello world*/ + 6 /**/)); + m_refreshedItems.clear(); +} + +// Refresh the root item, plus a hidden file, e.g. changing its icon. #190535 +void KDirListerTest::testRefreshRootItem() +{ + // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already. + m_refreshedItems.clear(); + m_refreshedItems2.clear(); + + // The item will be the root item of dirLister2, but also a child item + // of m_dirLister. + // In #190535 it would show "." instead of the subdir name, after a refresh... + const QString path = m_tempDir.path() + '/' + "subdir"; + MyDirLister dirLister2; + fillDirLister2(dirLister2, path); + + // Change the subdir by creating a file in it + waitUntilMTimeChange(path); + const QString foobar = path + "/.foobar"; + createSimpleFile(foobar); + + connect(&m_dirLister, SIGNAL(refreshItems(QList >)), + this, SLOT(slotRefreshItems(QList >))); + + // Arguably, the mtime change of "subdir" should lead to a refreshItem of subdir in the root dir. + // So the next line shouldn't be necessary, if KDirLister did this correctly. This isn't what this test is about though. + org::kde::KDirNotify::emitFilesChanged(QList() << QUrl::fromLocalFile(path)); + QVERIFY(waitForRefreshedItems()); + + QCOMPARE(m_dirLister.spyStarted.count(), 0); + QCOMPARE(m_dirLister.spyCompleted.count(), 0); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 0); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_refreshedItems.count(), 1); + QPair entry = m_refreshedItems.first(); + QCOMPARE(entry.first.url().toLocalFile(), path); + QCOMPARE(entry.first.name(), QString("subdir")); + QCOMPARE(entry.second.url().toLocalFile(), path); + QCOMPARE(entry.second.name(), QString("subdir")); + + QCOMPARE(m_refreshedItems2.count(), 1); + entry = m_refreshedItems2.first(); + QCOMPARE(entry.first.url().toLocalFile(), path); + QCOMPARE(entry.second.url().toLocalFile(), path); + // item name() doesn't matter here, it's the root item. + + m_refreshedItems.clear(); + m_refreshedItems2.clear(); + + waitUntilMTimeChange(path); + const QString directoryFile = path + "/.directory"; + createSimpleFile(directoryFile); + + org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(path)); + QTest::qWait(200); + // The order of these two is not deterministic + org::kde::KDirNotify::emitFilesChanged(QList() << QUrl::fromLocalFile(directoryFile)); + org::kde::KDirNotify::emitFilesChanged(QList() << QUrl::fromLocalFile(path)); + QVERIFY(waitForRefreshedItems()); + QCOMPARE(m_refreshedItems.count(), 1); + entry = m_refreshedItems.first(); + QCOMPARE(entry.first.url().toLocalFile(), path); + QCOMPARE(entry.second.url().toLocalFile(), path); + + m_refreshedItems.clear(); + m_refreshedItems2.clear(); + + // Note: this test leaves the .directory file as a side effect. + // Hidden though, shouldn't matter. +} + +void KDirListerTest::testDeleteItem() +{ + testOpenUrl(); // ensure m_items is uptodate + + const int origItemCount = m_items.count(); + QCOMPARE(fileCount(), origItemCount); + const QString path = m_tempDir.path() + '/'; + connect(&m_dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(exitLoop())); + + //qDebug() << "Removing " << path+"toplevelfile_1"; + QFile::remove(path + "toplevelfile_1"); + // the remove() doesn't always trigger kdirwatch in stat mode, if this all happens in the same second + KDirWatch::self()->setDirty(path); + if (m_dirLister.spyItemsDeleted.count() == 0) { + qDebug("waiting for itemsDeleted"); + enterLoop(); + } + + QCOMPARE(m_dirLister.spyItemsDeleted.count(), 1); + + // OK now kdirlister told us the file was deleted, let's try a re-listing + m_items.clear(); + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + QVERIFY(!m_dirLister.isFinished()); + connect(&m_dirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + QVERIFY(m_dirLister.isFinished()); + QCOMPARE(m_items.count(), origItemCount - 1); + + disconnect(&m_dirLister, 0, this, 0); + QCOMPARE(fileCount(), m_items.count()); +} + +void KDirListerTest::testRenameItem() +{ + m_refreshedItems2.clear(); + const QString dirPath = m_tempDir.path() + '/'; + connect(&m_dirLister, SIGNAL(refreshItems(QList >)), + this, SLOT(slotRefreshItems2(QList >))); + const QString path = dirPath + "toplevelfile_2"; + const QString newPath = dirPath + "toplevelfile_2.renamed.html"; + + KIO::SimpleJob *job = KIO::rename(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath), KIO::HideProgressInfo); + QVERIFY(job->exec()); + + QSignalSpy spyRefreshItems(&m_dirLister, SIGNAL(refreshItems(QList >))); + QVERIFY(spyRefreshItems.wait(2000)); + + QCOMPARE(m_refreshedItems2.count(), 1); + QPair entry = m_refreshedItems2.first(); + QCOMPARE(entry.first.url().toLocalFile(), path); + QCOMPARE(entry.first.mimetype(), QString("application/octet-stream")); + QCOMPARE(entry.second.url().toLocalFile(), newPath); + QCOMPARE(entry.second.mimetype(), QString("text/html")); + disconnect(&m_dirLister, 0, this, 0); + + // Let's see what KDirLister has in cache now + KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(newPath)); + QVERIFY(!cachedItem.isNull()); + QCOMPARE(cachedItem.url().toLocalFile(), newPath); + KFileItem oldCachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path)); + QVERIFY(oldCachedItem.isNull()); + m_refreshedItems2.clear(); +} + +void KDirListerTest::testRenameAndOverwrite() // has to be run after testRenameItem +{ + // Rename toplevelfile_2.renamed.html to toplevelfile_2, overwriting it. + const QString dirPath = m_tempDir.path() + '/'; + const QString path = dirPath + "toplevelfile_2"; + createTestFile(path); +#if WORKAROUND_BROKEN_INOTIFY + org::kde::KDirNotify::emitFilesAdded(dirPath); +#endif + KFileItem existingItem; + while (existingItem.isNull()) { + QTest::qWait(100); + existingItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path)); + }; + QCOMPARE(existingItem.url().toLocalFile(), path); + + m_refreshedItems.clear(); + connect(&m_dirLister, SIGNAL(refreshItems(QList >)), + this, SLOT(slotRefreshItems(QList >))); + const QString newPath = dirPath + "toplevelfile_2.renamed.html"; + + KIO::SimpleJob *job = KIO::rename(QUrl::fromLocalFile(newPath), QUrl::fromLocalFile(path), KIO::Overwrite | KIO::HideProgressInfo); + bool ok = job->exec(); + QVERIFY(ok); + + if (m_refreshedItems.isEmpty()) { + QVERIFY(waitForRefreshedItems()); // refreshItems could come from KDirWatch or KDirNotify. + } + + // Check that itemsDeleted was emitted -- preferably BEFORE refreshItems, + // but we can't easily check that with QSignalSpy... + QCOMPARE(m_dirLister.spyItemsDeleted.count(), 1); + + QCOMPARE(m_refreshedItems.count(), 1); + QPair entry = m_refreshedItems.first(); + QCOMPARE(entry.first.url().toLocalFile(), newPath); + QCOMPARE(entry.second.url().toLocalFile(), path); + disconnect(&m_dirLister, 0, this, 0); + + // Let's see what KDirLister has in cache now + KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path)); + QCOMPARE(cachedItem.url().toLocalFile(), path); + KFileItem oldCachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(newPath)); + QVERIFY(oldCachedItem.isNull()); + m_refreshedItems.clear(); +} + +void KDirListerTest::testConcurrentListing() +{ + const int origItemCount = m_items.count(); + QCOMPARE(fileCount(), origItemCount); + m_items.clear(); + m_items2.clear(); + + MyDirLister dirLister2; + + const QString path = m_tempDir.path() + '/'; + + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + connect(&dirLister2, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems2(KFileItemList))); + + // Before dirLister2 has time to emit the items, let's make m_dirLister move to another dir. + // This reproduces the use case "clicking on a folder in dolphin iconview, and dirlister2 + // is the one used by the "folder panel". m_dirLister is going to list the subdir, + // while dirLister2 wants to list the folder that m_dirLister has just left. + dirLister2.stop(); // like dolphin does, noop. + dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + m_dirLister.openUrl(QUrl::fromLocalFile(path + "subdir"), KDirLister::NoFlags); + + QCOMPARE(m_dirLister.spyStarted.count(), 1); + QCOMPARE(m_dirLister.spyCompleted.count(), 0); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_items.count(), 0); + + QCOMPARE(dirLister2.spyStarted.count(), 1); + QCOMPARE(dirLister2.spyCompleted.count(), 0); + QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0); + QCOMPARE(dirLister2.spyCanceled.count(), 0); + QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0); + QCOMPARE(dirLister2.spyClear.count(), 1); + QCOMPARE(dirLister2.spyClearQUrl.count(), 0); + QCOMPARE(m_items2.count(), 0); + QVERIFY(!m_dirLister.isFinished()); + QVERIFY(!dirLister2.isFinished()); + + // then wait for completed + qDebug("waiting for completed"); + connect(&m_dirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + connect(&dirLister2, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(2); + + //QCOMPARE(m_dirLister.spyStarted.count(), 1); // 2 when subdir is already in cache. + QCOMPARE(m_dirLister.spyCompleted.count(), 1); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_items.count(), 3); + + QCOMPARE(dirLister2.spyStarted.count(), 1); + QCOMPARE(dirLister2.spyCompleted.count(), 1); + QCOMPARE(dirLister2.spyCompletedQUrl.count(), 1); + QCOMPARE(dirLister2.spyCanceled.count(), 0); + QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0); + QCOMPARE(dirLister2.spyClear.count(), 1); + QCOMPARE(dirLister2.spyClearQUrl.count(), 0); + QCOMPARE(m_items2.count(), origItemCount); + if (!m_dirLister.isFinished()) { // false when an update is running because subdir is already in cache + // TODO check why this fails QVERIFY(m_dirLister.spyCanceled.wait(1000)); + QTest::qWait(1000); + } + + disconnect(&m_dirLister, 0, this, 0); + disconnect(&dirLister2, 0, this, 0); +} + +void KDirListerTest::testConcurrentHoldingListing() +{ + // #167851. + // A dirlister holding the items, and a second dirlister does + // openUrl(reload) (which triggers updateDirectory()) + // and the first lister immediately does openUrl() (which emits cached items). + + testOpenUrl(); // ensure m_dirLister holds the items. + const int origItemCount = m_items.count(); + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + m_items.clear(); + m_items2.clear(); + const QString path = m_tempDir.path() + '/'; + MyDirLister dirLister2; + connect(&dirLister2, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems2(KFileItemList))); + + dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); // will start a list job + QCOMPARE(dirLister2.spyStarted.count(), 1); + QCOMPARE(dirLister2.spyCompleted.count(), 0); + QCOMPARE(m_items.count(), 0); + QCOMPARE(m_items2.count(), 0); + + qDebug("calling m_dirLister.openUrl"); + m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // should emit cached items, and then "join" the running listjob + QCOMPARE(m_dirLister.spyStarted.count(), 1); + QCOMPARE(m_dirLister.spyCompleted.count(), 0); + QCOMPARE(m_items.count(), 0); + QCOMPARE(m_items2.count(), 0); + + qDebug("waiting for completed"); + connect(&dirLister2, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + + QCOMPARE(dirLister2.spyStarted.count(), 1); + QCOMPARE(dirLister2.spyCompleted.count(), 1); + QCOMPARE(dirLister2.spyCompletedQUrl.count(), 1); + QCOMPARE(dirLister2.spyCanceled.count(), 0); + QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0); + QCOMPARE(dirLister2.spyClear.count(), 1); + QCOMPARE(dirLister2.spyClearQUrl.count(), 0); + QCOMPARE(m_items2.count(), origItemCount); + + if (m_dirLister.spyCompleted.isEmpty()) { + connect(&m_dirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + } + + QCOMPARE(m_dirLister.spyStarted.count(), 1); + QCOMPARE(m_dirLister.spyCompleted.count(), 1); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QVERIFY(dirLister2.isFinished()); + QVERIFY(m_dirLister.isFinished()); + disconnect(&m_dirLister, 0, this, 0); + QCOMPARE(m_items.count(), origItemCount); +} + +void KDirListerTest::testConcurrentListingAndStop() +{ + m_items.clear(); + m_items2.clear(); + + MyDirLister dirLister2; + + // Use a new tempdir for this test, so that we don't use the cache at all. + QTemporaryDir tempDir; + const QString path = tempDir.path() + '/'; + createTestFile(path + "file_1"); + createTestFile(path + "file_2"); + createTestFile(path + "file_3"); + + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + connect(&dirLister2, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems2(KFileItemList))); + + // Before m_dirLister has time to emit the items, let's make dirLister2 call stop(). + // This should not stop the list job for m_dirLister (#267709). + dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); + m_dirLister.openUrl(QUrl::fromLocalFile(path)/*, KDirLister::Reload*/); + + QCOMPARE(m_dirLister.spyStarted.count(), 1); + QCOMPARE(m_dirLister.spyCompleted.count(), 0); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_items.count(), 0); + + QCOMPARE(dirLister2.spyStarted.count(), 1); + QCOMPARE(dirLister2.spyCompleted.count(), 0); + QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0); + QCOMPARE(dirLister2.spyCanceled.count(), 0); + QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0); + QCOMPARE(dirLister2.spyClear.count(), 1); + QCOMPARE(dirLister2.spyClearQUrl.count(), 0); + QCOMPARE(m_items2.count(), 0); + QVERIFY(!m_dirLister.isFinished()); + QVERIFY(!dirLister2.isFinished()); + + dirLister2.stop(); + + QCOMPARE(dirLister2.spyStarted.count(), 1); + QCOMPARE(dirLister2.spyCompleted.count(), 0); + QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0); + QCOMPARE(dirLister2.spyCanceled.count(), 1); + QCOMPARE(dirLister2.spyCanceledQUrl.count(), 1); + QCOMPARE(dirLister2.spyClear.count(), 1); + QCOMPARE(dirLister2.spyClearQUrl.count(), 0); + QCOMPARE(m_items2.count(), 0); + + // then wait for completed + qDebug("waiting for completed"); + connect(&m_dirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + + QCOMPARE(m_items.count(), 3); + QCOMPARE(m_items2.count(), 0); + + //QCOMPARE(m_dirLister.spyStarted.count(), 1); // 2 when in cache + QCOMPARE(m_dirLister.spyCompleted.count(), 1); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + + disconnect(&m_dirLister, 0, this, 0); +} + +void KDirListerTest::testDeleteListerEarly() +{ + // Do the same again, it should behave the same, even with the items in the cache + testOpenUrl(); + + // Start a second lister, it will get a cached items job, but delete it before the job can run + //qDebug() << "=========================================="; + { + m_items.clear(); + const QString path = m_tempDir.path() + '/'; + MyDirLister secondDirLister; + secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + QVERIFY(!secondDirLister.isFinished()); + } + //qDebug() << "=========================================="; + + // Check if we didn't keep the deleted dirlister in one of our lists. + // I guess the best way to do that is to just list the same dir again. + testOpenUrl(); +} + +void KDirListerTest::testOpenUrlTwice() +{ + // Calling openUrl(reload)+openUrl(normal) before listing even starts. + const int origItemCount = m_items.count(); + m_items.clear(); + const QString path = m_tempDir.path() + '/'; + MyDirLister secondDirLister; + connect(&secondDirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + + secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); // will start + QCOMPARE(secondDirLister.spyStarted.count(), 1); + QCOMPARE(secondDirLister.spyCompleted.count(), 0); + + qDebug("calling openUrl again"); + secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // will stop + start + + qDebug("waiting for completed"); + connect(&secondDirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + + QCOMPARE(secondDirLister.spyStarted.count(), 2); + QCOMPARE(secondDirLister.spyCompleted.count(), 1); + QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(secondDirLister.spyCanceled.count(), 0); // should not be emitted, see next test + QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(secondDirLister.spyClear.count(), 2); + QCOMPARE(secondDirLister.spyClearQUrl.count(), 0); + if (origItemCount) { // 0 if running this test separately + QCOMPARE(m_items.count(), origItemCount); + } + QVERIFY(secondDirLister.isFinished()); + disconnect(&secondDirLister, 0, this, 0); +} + +void KDirListerTest::testOpenUrlTwiceWithKeep() +{ + // Calling openUrl(reload)+openUrl(keep) on a new dir, + // before listing even starts (#177387) + // Well, in 177387 the second openUrl call was made from within slotCanceled + // called by the first openUrl + // (slotLoadingFinished -> setCurrentItem -> expandToUrl -> listDir), + // which messed things up in kdirlister (unexpected reentrancy). + m_items.clear(); + const QString path = m_tempDir.path() + "/newsubdir"; + QDir().mkdir(path); + MyDirLister secondDirLister; + connect(&secondDirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + + secondDirLister.openUrl(QUrl::fromLocalFile(path)); // will start a list job + QCOMPARE(secondDirLister.spyStarted.count(), 1); + QCOMPARE(secondDirLister.spyCanceled.count(), 0); + QCOMPARE(secondDirLister.spyCompleted.count(), 0); + + qDebug("calling openUrl again"); + secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::Keep); // stops and restarts the job + + qDebug("waiting for completed"); + connect(&secondDirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + + QCOMPARE(secondDirLister.spyStarted.count(), 2); + QCOMPARE(secondDirLister.spyCompleted.count(), 1); + QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1); + QCOMPARE(secondDirLister.spyCanceled.count(), 0); // should not be emitted, it led to recursion + QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(secondDirLister.spyClear.count(), 1); + QCOMPARE(secondDirLister.spyClearQUrl.count(), 1); + QCOMPARE(m_items.count(), 0); + QVERIFY(secondDirLister.isFinished()); + disconnect(&secondDirLister, 0, this, 0); + + QDir().remove(path); +} + +void KDirListerTest::testOpenAndStop() +{ + m_items.clear(); + const QString path = QStringLiteral("/"); // better not use a directory that we already listed! + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + qDebug() << "Calling stop!"; + m_dirLister.stop(); // we should also test stop(QUrl::fromLocalFile(path))... + + QCOMPARE(m_dirLister.spyStarted.count(), 1); // The call to openUrl itself, emits started + QCOMPARE(m_dirLister.spyCompleted.count(), 0); // we had time to stop before the job even started + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(m_dirLister.spyCanceled.count(), 1); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 1); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_items.count(), 0); // we had time to stop before the job even started + QVERIFY(m_dirLister.isFinished()); + disconnect(&m_dirLister, 0, this, 0); +} + +// A bug in the decAutoUpdate/incAutoUpdate logic made KDirLister stop watching a directory for changes, +// and never watch it again when opening it from the cache. +void KDirListerTest::testBug211472() +{ + m_items.clear(); + + QTemporaryDir newDir; + const QString path = newDir.path() + "/newsubdir/"; + QDir().mkdir(path); + MyDirLister dirLister; + connect(&dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + + dirLister.openUrl(QUrl::fromLocalFile(path)); + QSignalSpy spyCompleted(&dirLister, SIGNAL(completed())); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(dirLister.isFinished()); + QVERIFY(m_items.isEmpty()); + + if (true) { + // This block is required to trigger bug 211472. + + // Go 'up' to the parent of 'newsubdir'. + dirLister.openUrl(QUrl::fromLocalFile(newDir.path())); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(dirLister.isFinished()); + QVERIFY(!m_items.isEmpty()); + m_items.clear(); + + // Create a file in 'newsubdir' while we are listing its parent dir. + createTestFile(path + "newFile-1"); + // At this point, newsubdir is not used, so it's moved to the cache. + // This happens in checkUpdate, called when receiving a notification for the cached dir, + // this is why this unittest needs to create a test file in the subdir. + QTest::qWait(1000); + QVERIFY(m_items.isEmpty()); + + // Return to 'newsubdir'. It will be emitted from the cache, then an update will happen. + dirLister.openUrl(QUrl::fromLocalFile(path)); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(dirLister.isFinished()); + QCOMPARE(m_items.count(), 1); + m_items.clear(); + } + + // Now try to create a second file in 'newsubdir' and verify that the + // dir lister notices it. + QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything. + + createTestFile(path + "newFile-2"); + + int numTries = 0; + // Give time for KDirWatch to notify us + while (m_items.isEmpty()) { + QVERIFY(++numTries < 10); + QTest::qWait(200); + } + QCOMPARE(m_items.count(), 1); + + newDir.remove(); + QSignalSpy spyClear(&dirLister, SIGNAL(clear())); + QVERIFY(spyClear.wait(1000)); +} + +void KDirListerTest::testRenameCurrentDir() // #294445 +{ + m_items.clear(); + + const QString path = m_tempDir.path() + "/newsubdir-1"; + QVERIFY(QDir().mkdir(path)); + MyDirLister secondDirLister; + connect(&secondDirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + + secondDirLister.openUrl(QUrl::fromLocalFile(path)); + QSignalSpy spyCompleted(&secondDirLister, SIGNAL(completed())); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(secondDirLister.isFinished()); + QVERIFY(m_items.empty()); + QCOMPARE(secondDirLister.rootItem().url().toLocalFile(), path); + + const QString newPath = m_tempDir.path() + "/newsubdir-2"; + QVERIFY(QDir().rename(path, newPath)); + org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath)); + QSignalSpy spyRedirection(&secondDirLister, SIGNAL(redirection(QUrl,QUrl))); + QVERIFY(spyRedirection.wait(1000)); + + // Check that the URL of the root item got updated + QCOMPARE(secondDirLister.rootItem().url().toLocalFile(), newPath); + + disconnect(&secondDirLister, 0, this, 0); + QDir().rmdir(newPath); +} + +void KDirListerTest::slotOpenUrlOnRename(const QUrl &newUrl) +{ + QVERIFY(m_dirLister.openUrl(newUrl)); +} + +//This tests for a crash if you connect redirects to openUrl, due +//to internal data being inconsistently exposed. +//Matches usage in gwenview. +void KDirListerTest::testRenameCurrentDirOpenUrl() +{ + m_items.clear(); + const QString path = m_tempDir.path() + "/newsubdir-1/"; + QVERIFY(QDir().mkdir(path)); + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + + m_dirLister.openUrl(QUrl::fromLocalFile(path)); + QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(m_dirLister.isFinished()); + + const QString newPath = m_tempDir.path() + "/newsubdir-2"; + QVERIFY(QDir().rename(path, newPath)); + + org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath)); + + //Connect the redirection to openURL, so that on a rename the new location is opened. + //This matches usage in gwenview, and crashes + connect(&m_dirLister, SIGNAL(redirection(QUrl)), this, SLOT(slotOpenUrlOnRename(QUrl))); + connect(&m_dirLister, SIGNAL(redirection(QUrl)), this, SLOT(exitLoop())); + //Enter loop to get the redirection signal. + enterLoop(); + disconnect(&m_dirLister, 0, this, 0); + QDir().rmdir(newPath); +} + +void KDirListerTest::testRedirection() +{ + m_items.clear(); + const QUrl url(QStringLiteral("file://somemachine/")); + + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("smb"))) { + QSKIP("smb not installed"); + } + + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + // The call to openUrl itself, emits started + m_dirLister.openUrl(url, KDirLister::NoFlags); + + QCOMPARE(m_dirLister.spyStarted.count(), 1); + QCOMPARE(m_dirLister.spyCompleted.count(), 0); + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_dirLister.spyRedirection.count(), 0); + QCOMPARE(m_items.count(), 0); + QVERIFY(!m_dirLister.isFinished()); + + // then wait for the redirection signal + qDebug("waiting for redirection"); + connect(&m_dirLister, SIGNAL(redirection(QUrl,QUrl)), this, SLOT(exitLoop())); + enterLoop(); + QCOMPARE(m_dirLister.spyStarted.count(), 1); + QCOMPARE(m_dirLister.spyCompleted.count(), 0); // we stopped before the listing. + QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); + QCOMPARE(m_dirLister.spyCanceled.count(), 0); + QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); + QCOMPARE(m_dirLister.spyClear.count(), 2); // redirection cleared a second time (just in case...) + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QCOMPARE(m_dirLister.spyRedirection.count(), 1); + QVERIFY(m_items.isEmpty()); + QVERIFY(!m_dirLister.isFinished()); + + m_dirLister.stop(url); + QVERIFY(!m_dirLister.isFinished()); + disconnect(&m_dirLister, 0, this, 0); +} + +void KDirListerTest::testListEmptyDirFromCache() // #278431 +{ + m_items.clear(); + + QTemporaryDir newDir; + const QUrl url = QUrl::fromLocalFile(newDir.path()); + + // List and watch an empty dir + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + m_dirLister.openUrl(url); + QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(m_dirLister.isFinished()); + QVERIFY(m_items.isEmpty()); + + // List it with two more dirlisters (one will create a cached items job, the second should also benefit from it) + MyDirLister secondDirLister; + connect(&secondDirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + secondDirLister.openUrl(url); + MyDirLister thirdDirLister; + connect(&thirdDirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + thirdDirLister.openUrl(url); + + // The point of this test is that (with DEBUG_CACHE enabled) it used to assert here + // with "HUH? Lister KDirLister(0x7ffd1f044260) is supposed to be listing, but has no job!" + // due to the if (!itemU->lstItems.isEmpty()) check which is now removed. + + QVERIFY(!secondDirLister.isFinished()); // we didn't go to the event loop yet + QSignalSpy spySecondCompleted(&secondDirLister, SIGNAL(completed())); + QVERIFY(spySecondCompleted.wait(1000)); + if (!thirdDirLister.isFinished()) { + QSignalSpy spyThirdCompleted(&thirdDirLister, SIGNAL(completed())); + QVERIFY(spyThirdCompleted.wait(1000)); + } +} + +void KDirListerTest::testWatchingAfterCopyJob() // #331582 +{ + m_items.clear(); + + QTemporaryDir newDir; + const QString path = newDir.path() + '/'; + + // List and watch an empty dir + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + m_dirLister.openUrl(QUrl::fromLocalFile(path)); + QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(m_dirLister.isFinished()); + QVERIFY(m_items.isEmpty()); + + // Create three subfolders. + QVERIFY(QDir().mkdir(path + "New Folder")); + QVERIFY(QDir().mkdir(path + "New Folder 1")); + QVERIFY(QDir().mkdir(path + "New Folder 2")); + + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(m_dirLister.isFinished()); + QCOMPARE(m_items.count(), 3); + + // Create a new file and verify that the dir lister notices it. + m_items.clear(); + createTestFile(path + "a"); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(m_dirLister.isFinished()); + QCOMPARE(m_items.count(), 1); + + // Rename one of the subfolders. + const QString oldPath = path + "New Folder 1"; + const QString newPath = path + "New Folder 1a"; + + // NOTE: The following two lines are required to trigger the bug! + KIO::Job *job = KIO::moveAs(QUrl::fromLocalFile(oldPath), QUrl::fromLocalFile(newPath), KIO::HideProgressInfo); + job->exec(); + + // Now try to create a second new file and verify that the + // dir lister notices it. + m_items.clear(); + createTestFile(path + "b"); + + int numTries = 0; + // Give time for KDirWatch to notify us + // This should end up in "KCoreDirListerCache::slotFileDirty" + while (m_items.isEmpty()) { + QVERIFY(++numTries < 10); + QTest::qWait(200); + } + QCOMPARE(m_items.count(), 1); + + newDir.remove(); + QSignalSpy clearSpy(&m_dirLister, SIGNAL(clear())); + QVERIFY(clearSpy.wait(1000)); +} + +void KDirListerTest::testRemoveWatchedDirectory() +{ + m_items.clear(); + + QTemporaryDir newDir; + const QString path = newDir.path() + '/'; + + // List and watch an empty dir + connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + m_dirLister.openUrl(QUrl::fromLocalFile(path)); + QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(m_dirLister.isFinished()); + QVERIFY(m_items.isEmpty()); + + // Create a subfolder. + const QString subDirPath = path + "abc"; + QVERIFY(QDir().mkdir(subDirPath)); + + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(m_dirLister.isFinished()); + QCOMPARE(m_items.count(), 1); + const KFileItem item = m_items.at(0); + + // Watch the subfolder for changes, independently. + // This is what triggers the bug. + // (Technically, this could become a KDirWatch unittest, but if one day we use QFSW, good to have the tests here) + KDirWatch watcher; + watcher.addDir(subDirPath); + + // Remove the subfolder. + m_items.clear(); + QVERIFY(QDir().rmdir(path + "abc")); + + // This should trigger an update. + QVERIFY(spyCompleted.wait(1000)); + QVERIFY(m_dirLister.isFinished()); + QCOMPARE(m_items.count(), 0); + QCOMPARE(m_dirLister.spyItemsDeleted.count(), 1); + const KFileItem deletedItem = m_dirLister.spyItemsDeleted.at(0).at(0).value().at(0); + QCOMPARE(item, deletedItem); +} + +void KDirListerTest::testDirPermissionChange() +{ + QTemporaryDir tempDir; + + const QString path = tempDir.path() + '/'; + const QString subdir = path + QLatin1String("subdir"); + QVERIFY(QDir().mkdir(subdir)); + + MyDirLister mylister; + mylister.openUrl(QUrl::fromLocalFile(tempDir.path())); + QSignalSpy spyCompleted(&mylister, SIGNAL(completed())); + QVERIFY(spyCompleted.wait(1000)); + + KFileItemList list = mylister.items(); + QVERIFY(mylister.isFinished()); + QCOMPARE(list.count(), 1); + QCOMPARE(mylister.rootItem().url().toLocalFile(), tempDir.path()); + + const mode_t permissions = (S_IRUSR | S_IWUSR | S_IXUSR); + KIO::SimpleJob *job = KIO::chmod(list.first().url(), permissions); + QVERIFY(job->exec()); + + QSignalSpy spyRefreshItems(&mylister, SIGNAL(refreshItems(QList >))); + QVERIFY(spyRefreshItems.wait(2000)); + + list = mylister.items(); + QCOMPARE(list.first().permissions(), permissions); + QVERIFY(QDir().rmdir(subdir)); +} + +void KDirListerTest::enterLoop(int exitCount) +{ + //qDebug("enterLoop"); + m_exitCount = exitCount; + m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents); +} + +void KDirListerTest::exitLoop() +{ + //qDebug("exitLoop"); + --m_exitCount; + if (m_exitCount <= 0) { + m_eventLoop.quit(); + } +} + +void KDirListerTest::slotNewItems(const KFileItemList &lst) +{ + m_items += lst; +} + +void KDirListerTest::slotNewItems2(const KFileItemList &lst) +{ + m_items2 += lst; +} + +void KDirListerTest::slotRefreshItems(const QList > &lst) +{ + m_refreshedItems += lst; + emit refreshItemsReceived(); +} + +void KDirListerTest::slotRefreshItems2(const QList > &lst) +{ + m_refreshedItems2 += lst; +} + +void KDirListerTest::testCopyAfterListingAndMove() // #353195 +{ + const QString dirA = m_tempDir.path() + "/a"; + QVERIFY(QDir().mkdir(dirA)); + const QString dirB = m_tempDir.path() + "/b"; + QVERIFY(QDir().mkdir(dirB)); + + // ensure m_dirLister holds the items. + m_dirLister.openUrl(QUrl::fromLocalFile(path()), KDirLister::NoFlags); + QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); + QVERIFY(spyCompleted.wait()); + + // Move b into a + KIO::Job *moveJob = KIO::move(QUrl::fromLocalFile(dirB), QUrl::fromLocalFile(dirA)); + moveJob->setUiDelegate(0); + QVERIFY(moveJob->exec()); + QVERIFY(QFileInfo(m_tempDir.path() + "/a/b").isDir()); + + // Give some time to processPendingUpdates + QTest::qWait(1000); + + // Copy folder a elsewhere + const QString dest = m_tempDir.path() + "/subdir"; + KIO::Job *copyJob = KIO::copy(QUrl::fromLocalFile(dirA), QUrl::fromLocalFile(dest)); + copyJob->setUiDelegate(0); + QVERIFY(copyJob->exec()); + QVERIFY(QFileInfo(m_tempDir.path() + "/subdir/a/b").isDir()); +} + +void KDirListerTest::testDeleteCurrentDir() +{ + // ensure m_dirLister holds the items. + m_dirLister.openUrl(QUrl::fromLocalFile(path()), KDirLister::NoFlags); + connect(&m_dirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + disconnect(&m_dirLister, SIGNAL(completed()), this, SLOT(exitLoop())); + + m_dirLister.clearSpies(); + connect(&m_dirLister, SIGNAL(clear()), &m_eventLoop, SLOT(quit())); + KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(path()), KIO::HideProgressInfo); + bool ok = job->exec(); + QVERIFY(ok); + enterLoop(); + QCOMPARE(m_dirLister.spyClear.count(), 1); + QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); + QList deletedUrls; + for (int i = 0; i < m_dirLister.spyItemsDeleted.count(); ++i) { + deletedUrls += m_dirLister.spyItemsDeleted[i][0].value().urlList(); + } + //qDebug() << deletedUrls; + QUrl currentDirUrl = QUrl::fromLocalFile(path()).adjusted(QUrl::StripTrailingSlash); + // Sometimes I get ("current/subdir", "current") here, but that seems ok. + QVERIFY(deletedUrls.contains(currentDirUrl)); +} + +int KDirListerTest::fileCount() const +{ + return QDir(path()).entryList(QDir::AllEntries | QDir::NoDotAndDotDot).count(); +} + +bool KDirListerTest::waitForRefreshedItems() +{ + int numTries = 0; + // Give time for KDirWatch to notify us + while (m_refreshedItems.isEmpty()) { + if (++numTries == 20) { + return false; + } + QTest::qWait(100); + } + return true; +} + +void KDirListerTest::createSimpleFile(const QString &fileName) +{ + QFile file(fileName); + QVERIFY(file.open(QIODevice::WriteOnly)); + file.write(QByteArray("foo")); + file.close(); +} + +void KDirListerTest::fillDirLister2(MyDirLister &lister, const QString &path) +{ + m_items2.clear(); + connect(&lister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems2(KFileItemList))); + connect(&lister, SIGNAL(refreshItems(QList >)), + this, SLOT(slotRefreshItems2(QList >))); + lister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + connect(&lister, SIGNAL(completed()), this, SLOT(exitLoop())); + enterLoop(); + QVERIFY(lister.isFinished()); +} + +void KDirListerTest::waitUntilMTimeChange(const QString &path) +{ + // Wait until the current second is more than the file's mtime + // otherwise this change will go unnoticed + + QFileInfo fi(path); + QVERIFY(fi.exists()); + const QDateTime ctime = qMax(fi.lastModified(), fi.created()); + waitUntilAfter(ctime); +} + +void KDirListerTest::waitUntilAfter(const QDateTime &ctime) +{ + int totalWait = 0; + QDateTime now; + Q_FOREVER { + now = QDateTime::currentDateTime(); + if (now.toTime_t() == ctime.toTime_t()) { // truncate milliseconds + totalWait += 50; + QTest::qWait(50); + } else { + QVERIFY(now > ctime); // can't go back in time ;) + QTest::qWait(50); // be safe + break; + } + } + //if (totalWait > 0) + qDebug() << "Waited" << totalWait << "ms so that now" << now.toString(Qt::ISODate) << "is >" << ctime.toString(Qt::ISODate); +} + +#include "moc_kdirlistertest.cpp" diff --git a/autotests/kdirlistertest.h b/autotests/kdirlistertest.h new file mode 100644 index 0000000..de2f16d --- /dev/null +++ b/autotests/kdirlistertest.h @@ -0,0 +1,151 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef KDIRLISTERTEST_H +#define KDIRLISTERTEST_H + +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(KFileItemList) + +class GlobalInits +{ +public: + GlobalInits() + { + // Must be done before the QSignalSpys connect + qRegisterMetaType(); + qRegisterMetaType(); + } +}; + +class MyDirLister : public KDirLister, GlobalInits +{ +public: + MyDirLister() + : spyStarted(this, SIGNAL(started(QUrl))), + spyClear(this, SIGNAL(clear())), + spyClearQUrl(this, SIGNAL(clear(QUrl))), + spyCompleted(this, SIGNAL(completed())), + spyCompletedQUrl(this, SIGNAL(completed(QUrl))), + spyCanceled(this, SIGNAL(canceled())), + spyCanceledQUrl(this, SIGNAL(canceled(QUrl))), + spyRedirection(this, SIGNAL(redirection(QUrl))), + spyItemsDeleted(this, SIGNAL(itemsDeleted(KFileItemList))) + {} + + void clearSpies() + { + spyStarted.clear(); + spyClear.clear(); + spyClearQUrl.clear(); + spyCompleted.clear(); + spyCompletedQUrl.clear(); + spyCanceled.clear(); + spyCanceledQUrl.clear(); + spyRedirection.clear(); + spyItemsDeleted.clear(); + } + + QSignalSpy spyStarted; + QSignalSpy spyClear; + QSignalSpy spyClearQUrl; + QSignalSpy spyCompleted; + QSignalSpy spyCompletedQUrl; + QSignalSpy spyCanceled; + QSignalSpy spyCanceledQUrl; + QSignalSpy spyRedirection; + QSignalSpy spyItemsDeleted; +protected: + virtual void handleError(KIO::Job *job); +}; + +class KDirListerTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void cleanup(); + void testOpenUrl(); + void testOpenUrlFromCache(); + void testNewItems(); + void testNewItemByCopy(); + void testNewItemsInSymlink(); + void testRefreshItems(); + void testRefreshRootItem(); + void testDeleteItem(); + void testRenameItem(); + void testRenameAndOverwrite(); + void testConcurrentListing(); + void testConcurrentHoldingListing(); + void testConcurrentListingAndStop(); + void testDeleteListerEarly(); + void testOpenUrlTwice(); + void testOpenUrlTwiceWithKeep(); + void testOpenAndStop(); + void testBug211472(); + void testRenameCurrentDir(); + void testRenameCurrentDirOpenUrl(); + void testRedirection(); + void testListEmptyDirFromCache(); + void testWatchingAfterCopyJob(); + void testRemoveWatchedDirectory(); + void testDirPermissionChange(); + void testCopyAfterListingAndMove(); // #353195 + void testDeleteCurrentDir(); // must be last! + +protected Q_SLOTS: // 'more private than private slots' - i.e. not seen by qtestlib + void exitLoop(); + void slotNewItems(const KFileItemList &); + void slotNewItems2(const KFileItemList &); + void slotRefreshItems(const QList > &); + void slotRefreshItems2(const QList > &); + void slotOpenUrlOnRename(const QUrl &); + +Q_SIGNALS: + void refreshItemsReceived(); + +private: + void enterLoop(int exitCount = 1); + int fileCount() const; + QString path() const + { + return m_tempDir.path() + '/'; + } + bool waitForRefreshedItems(); + void createSimpleFile(const QString &fileName); + void fillDirLister2(MyDirLister &lister, const QString &path); + void waitUntilMTimeChange(const QString &path); + void waitUntilAfter(const QDateTime &ctime); + +private: + int m_exitCount; + QEventLoop m_eventLoop; + QTemporaryDir m_tempDir; + MyDirLister m_dirLister; + KFileItemList m_items; + KFileItemList m_items2; + QList > m_refreshedItems, m_refreshedItems2; +}; + +#endif diff --git a/autotests/kdirmodeltest.cpp b/autotests/kdirmodeltest.cpp new file mode 100644 index 0000000..d814d67 --- /dev/null +++ b/autotests/kdirmodeltest.cpp @@ -0,0 +1,1529 @@ +/* This file is part of the KDE project + Copyright 2006 - 2007 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kdirmodeltest.h" +#include +#include +#include +#include +#include +#include +//TODO #include "../../kdeui/tests/proxymodeltestsuite/modelspy.h" + +#include + +#ifdef Q_OS_UNIX +#include +#endif +#include +#include +#include +#include +#include "kiotesthelper.h" +#include + +QTEST_MAIN(KDirModelTest) + +#ifndef USE_QTESTEVENTLOOP +#define exitLoop quit +#endif + +#define checkedConnect(a,b,c,d) QVERIFY(QObject::connect(a,b,c,d)) + +#ifndef Q_OS_WIN +#define SPECIALCHARS " special chars%:.pdf" +#else +#define SPECIALCHARS " special chars%.pdf" +#endif + +Q_DECLARE_METATYPE(KFileItemList) + +void KDirModelTest::initTestCase() +{ + qputenv("LC_ALL", "en_US.UTF-8"); + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + + qRegisterMetaType("KFileItemList"); + + m_dirModelForExpand = 0; + m_dirModel = 0; + s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago + m_tempDir = 0; + m_topLevelFileNames << QStringLiteral("toplevelfile_1") + << QStringLiteral("toplevelfile_2") + << QStringLiteral("toplevelfile_3") + << SPECIALCHARS + ; + recreateTestData(); + + fillModel(false); +} + +void KDirModelTest::recreateTestData() +{ + if (m_tempDir) { + qDebug() << "Deleting old tempdir" << m_tempDir->path(); + delete m_tempDir; + qApp->processEvents(); // process inotify events so they don't pollute us later on + } + + m_tempDir = new QTemporaryDir; + qDebug() << "new tmp dir:" << m_tempDir->path(); + // Create test data: + /* + * PATH/toplevelfile_1 + * PATH/toplevelfile_2 + * PATH/toplevelfile_3 + * PATH/special chars%:.pdf + * PATH/.hiddenfile + * PATH/.hiddenfile2 + * PATH/subdir + * PATH/subdir/testfile + * PATH/subdir/testsymlink + * PATH/subdir/subsubdir + * PATH/subdir/subsubdir/testfile + */ + const QString path = m_tempDir->path() + '/'; + foreach (const QString &f, m_topLevelFileNames) { + createTestFile(path + f); + } + createTestFile(path + ".hiddenfile"); + createTestFile(path + ".hiddenfile2"); + createTestDirectory(path + "subdir"); + createTestDirectory(path + "subdir/subsubdir", NoSymlink); + + m_dirIndex = QModelIndex(); + m_fileIndex = QModelIndex(); + m_secondFileIndex = QModelIndex(); +} + +void KDirModelTest::cleanupTestCase() +{ + delete m_tempDir; + m_tempDir = 0; + delete m_dirModel; + m_dirModel = 0; +} + +void KDirModelTest::fillModel(bool reload, bool expectAllIndexes) +{ + if (!m_dirModel) { + m_dirModel = new KDirModel; + } + m_dirModel->dirLister()->setAutoErrorHandlingEnabled(false, 0); + const QString path = m_tempDir->path() + '/'; + KDirLister *dirLister = m_dirModel->dirLister(); + qDebug() << "Calling openUrl"; + dirLister->openUrl(QUrl::fromLocalFile(path), reload ? KDirLister::Reload : KDirLister::NoFlags); + checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + qDebug() << "enterLoop, waiting for completed()"; + enterLoop(); + + if (expectAllIndexes) { + collectKnownIndexes(); + } + disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); +} + +// Called after test function +void KDirModelTest::cleanup() +{ + if (m_dirModel) { + disconnect(m_dirModel, 0, &m_eventLoop, 0); + disconnect(m_dirModel->dirLister(), 0, this, 0); + m_dirModel->dirLister()->setNameFilter(QString()); + m_dirModel->dirLister()->setMimeFilter(QStringList()); + m_dirModel->dirLister()->emitChanges(); + } +} + +void KDirModelTest::collectKnownIndexes() +{ + m_dirIndex = QModelIndex(); + m_fileIndex = QModelIndex(); + m_secondFileIndex = QModelIndex(); + // Create the indexes once and for all + // The trouble is that the order of listing is undefined, one can get 1/2/3/subdir or subdir/3/2/1 for instance. + for (int row = 0; row < m_topLevelFileNames.count() + 1 /*subdir*/; ++row) { + QModelIndex idx = m_dirModel->index(row, 0, QModelIndex()); + QVERIFY(idx.isValid()); + KFileItem item = m_dirModel->itemForIndex(idx); + qDebug() << item.url() << "isDir=" << item.isDir(); + QString fileName = item.url().fileName(); + if (item.isDir()) { + m_dirIndex = idx; + } else if (fileName == QLatin1String("toplevelfile_1")) { + m_fileIndex = idx; + } else if (fileName == QLatin1String("toplevelfile_2")) { + m_secondFileIndex = idx; + } else if (fileName.startsWith(QLatin1String(" special"))) { + m_specialFileIndex = idx; + } + } + QVERIFY(m_dirIndex.isValid()); + QVERIFY(m_fileIndex.isValid()); + QVERIFY(m_secondFileIndex.isValid()); + QVERIFY(m_specialFileIndex.isValid()); + + // Now list subdir/ + QVERIFY(m_dirModel->canFetchMore(m_dirIndex)); + m_dirModel->fetchMore(m_dirIndex); + qDebug() << "Listing subdir/"; + enterLoop(); + + // Index of a file inside a directory (subdir/testfile) + QModelIndex subdirIndex; + m_fileInDirIndex = QModelIndex(); + for (int row = 0; row < 3; ++row) { + QModelIndex idx = m_dirModel->index(row, 0, m_dirIndex); + if (m_dirModel->itemForIndex(idx).isDir()) { + subdirIndex = idx; + } else if (m_dirModel->itemForIndex(idx).name() == QLatin1String("testfile")) { + m_fileInDirIndex = idx; + } + } + + // List subdir/subsubdir + QVERIFY(m_dirModel->canFetchMore(subdirIndex)); + qDebug() << "Listing subdir/subsubdir"; + m_dirModel->fetchMore(subdirIndex); + enterLoop(); + + // Index of ... well, subdir/subsubdir/testfile + m_fileInSubdirIndex = m_dirModel->index(0, 0, subdirIndex); +} + +void KDirModelTest::enterLoop() +{ +#ifdef USE_QTESTEVENTLOOP + m_eventLoop.enterLoop(10 /*seconds max*/); + QVERIFY(!m_eventLoop.timeout()); +#else + m_eventLoop.exec(); +#endif +} + +void KDirModelTest::slotListingCompleted() +{ + qDebug(); +#ifdef USE_QTESTEVENTLOOP + m_eventLoop.exitLoop(); +#else + m_eventLoop.quit(); +#endif +} + +void KDirModelTest::testRowCount() +{ + const int topLevelRowCount = m_dirModel->rowCount(); + QCOMPARE(topLevelRowCount, m_topLevelFileNames.count() + 1 /*subdir*/); + const int subdirRowCount = m_dirModel->rowCount(m_dirIndex); + QCOMPARE(subdirRowCount, 3); + + QVERIFY(m_fileIndex.isValid()); + const int fileRowCount = m_dirModel->rowCount(m_fileIndex); // #176555 + QCOMPARE(fileRowCount, 0); +} + +void KDirModelTest::testIndex() +{ + QVERIFY(m_dirModel->hasChildren()); + + // Index of the first file + QVERIFY(m_fileIndex.isValid()); + QCOMPARE(m_fileIndex.model(), static_cast(m_dirModel)); + //QCOMPARE(m_fileIndex.row(), 0); + QCOMPARE(m_fileIndex.column(), 0); + QVERIFY(!m_fileIndex.parent().isValid()); + QVERIFY(!m_dirModel->hasChildren(m_fileIndex)); + + // Index of a directory + QVERIFY(m_dirIndex.isValid()); + QCOMPARE(m_dirIndex.model(), static_cast(m_dirModel)); + //QCOMPARE(m_dirIndex.row(), 3); // ordering isn't guaranteed + QCOMPARE(m_dirIndex.column(), 0); + QVERIFY(!m_dirIndex.parent().isValid()); + QVERIFY(m_dirModel->hasChildren(m_dirIndex)); + + // Index of a file inside a directory (subdir/testfile) + QVERIFY(m_fileInDirIndex.isValid()); + QCOMPARE(m_fileInDirIndex.model(), static_cast(m_dirModel)); + //QCOMPARE(m_fileInDirIndex.row(), 0); // ordering isn't guaranteed + QCOMPARE(m_fileInDirIndex.column(), 0); + QVERIFY(m_fileInDirIndex.parent() == m_dirIndex); + QVERIFY(!m_dirModel->hasChildren(m_fileInDirIndex)); + + // Index of subdir/subsubdir/testfile + QVERIFY(m_fileInSubdirIndex.isValid()); + QCOMPARE(m_fileInSubdirIndex.model(), static_cast(m_dirModel)); + QCOMPARE(m_fileInSubdirIndex.row(), 0); // we can check it because it's the only file there + QCOMPARE(m_fileInSubdirIndex.column(), 0); + QVERIFY(m_fileInSubdirIndex.parent().parent() == m_dirIndex); + QVERIFY(!m_dirModel->hasChildren(m_fileInSubdirIndex)); + + // Test sibling() by going from subdir/testfile to subdir/subsubdir + const QModelIndex subsubdirIndex = m_fileInSubdirIndex.parent(); + QVERIFY(subsubdirIndex.isValid()); + QModelIndex sibling1 = m_dirModel->sibling(subsubdirIndex.row(), 0, m_fileInDirIndex); + QVERIFY(sibling1.isValid()); + QVERIFY(sibling1 == subsubdirIndex); + + // Invalid sibling call + QVERIFY(!m_dirModel->sibling(1, 0, m_fileInSubdirIndex).isValid()); + + // Test index() with a valid parent (dir). + QModelIndex index2 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), subsubdirIndex); + QVERIFY(index2.isValid()); + QVERIFY(index2 == m_fileInSubdirIndex); + + // Test index() with a non-parent (file). + QModelIndex index3 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), m_fileIndex); + QVERIFY(!index3.isValid()); +} + +void KDirModelTest::testNames() +{ + QString fileName = m_dirModel->data(m_fileIndex, Qt::DisplayRole).toString(); + QCOMPARE(fileName, QString("toplevelfile_1")); + + QString specialFileName = m_dirModel->data(m_specialFileIndex, Qt::DisplayRole).toString(); + QCOMPARE(specialFileName, QString(SPECIALCHARS)); + + QString dirName = m_dirModel->data(m_dirIndex, Qt::DisplayRole).toString(); + QCOMPARE(dirName, QString("subdir")); + + QString fileInDirName = m_dirModel->data(m_fileInDirIndex, Qt::DisplayRole).toString(); + QCOMPARE(fileInDirName, QString("testfile")); + + QString fileInSubdirName = m_dirModel->data(m_fileInSubdirIndex, Qt::DisplayRole).toString(); + QCOMPARE(fileInSubdirName, QString("testfile")); +} + +void KDirModelTest::testItemForIndex() +{ + // root item + KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); + QVERIFY(!rootItem.isNull()); + QCOMPARE(rootItem.name(), QString(".")); + + KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); + QVERIFY(!fileItem.isNull()); + QCOMPARE(fileItem.name(), QString("toplevelfile_1")); + QVERIFY(!fileItem.isDir()); + QCOMPARE(fileItem.url().toLocalFile(), QString(m_tempDir->path() + "/toplevelfile_1")); + + KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex); + QVERIFY(!dirItem.isNull()); + QCOMPARE(dirItem.name(), QString("subdir")); + QVERIFY(dirItem.isDir()); + QCOMPARE(dirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir")); + + KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex); + QVERIFY(!fileInDirItem.isNull()); + QCOMPARE(fileInDirItem.name(), QString("testfile")); + QVERIFY(!fileInDirItem.isDir()); + QCOMPARE(fileInDirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir/testfile")); + + KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex); + QVERIFY(!fileInSubdirItem.isNull()); + QCOMPARE(fileInSubdirItem.name(), QString("testfile")); + QVERIFY(!fileInSubdirItem.isDir()); + QCOMPARE(fileInSubdirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir/subsubdir/testfile")); +} + +void KDirModelTest::testIndexForItem() +{ + KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); + QModelIndex rootIndex = m_dirModel->indexForItem(rootItem); + QVERIFY(!rootIndex.isValid()); + + KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); + QModelIndex fileIndex = m_dirModel->indexForItem(fileItem); + QCOMPARE(fileIndex, m_fileIndex); + + KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex); + QModelIndex dirIndex = m_dirModel->indexForItem(dirItem); + QCOMPARE(dirIndex, m_dirIndex); + + KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex); + QModelIndex fileInDirIndex = m_dirModel->indexForItem(fileInDirItem); + QCOMPARE(fileInDirIndex, m_fileInDirIndex); + + KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex); + QModelIndex fileInSubdirIndex = m_dirModel->indexForItem(fileInSubdirItem); + QCOMPARE(fileInSubdirIndex, m_fileInSubdirIndex); +} + +void KDirModelTest::testData() +{ + // First file + QModelIndex idx1col0 = m_dirModel->index(m_fileIndex.row(), 0, QModelIndex()); + QCOMPARE(idx1col0.data().toString(), QString("toplevelfile_1")); + QModelIndex idx1col1 = m_dirModel->index(m_fileIndex.row(), 1, QModelIndex()); + QString size1 = m_dirModel->data(idx1col1, Qt::DisplayRole).toString(); + QCOMPARE(size1, QString("11 B")); + + KFileItem item = m_dirModel->data(m_fileIndex, KDirModel::FileItemRole).value(); + KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); + QCOMPARE(item, fileItem); + + QCOMPARE(m_dirModel->data(m_fileIndex, KDirModel::ChildCountRole).toInt(), (int)KDirModel::ChildCountUnknown); + + // Second file + QModelIndex idx2col0 = m_dirModel->index(m_secondFileIndex.row(), 0, QModelIndex()); + QString display2 = m_dirModel->data(idx2col0, Qt::DisplayRole).toString(); + QCOMPARE(display2, QString("toplevelfile_2")); + + // Subdir: check child count + QCOMPARE(m_dirModel->data(m_dirIndex, KDirModel::ChildCountRole).toInt(), 3); + + // Subsubdir: check child count + QCOMPARE(m_dirModel->data(m_fileInSubdirIndex.parent(), KDirModel::ChildCountRole).toInt(), 1); +} + +void KDirModelTest::testReload() +{ + fillModel(true); + testItemForIndex(); +} + +// We want more info than just "the values differ", if they do. +#define COMPARE_INDEXES(a, b) \ + QCOMPARE(a.row(), b.row()); \ + QCOMPARE(a.column(), b.column()); \ + QCOMPARE(a.model(), b.model()); \ + QCOMPARE(a.parent().isValid(), b.parent().isValid()); \ + QCOMPARE(a, b); + +void KDirModelTest::testModifyFile() +{ + const QString file = m_tempDir->path() + "/toplevelfile_2"; + const QUrl url = QUrl::fromLocalFile(file); + +#if 1 + QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); +#else + ModelSpy modelSpy(m_dirModel); +#endif + checkedConnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + &m_eventLoop, SLOT(exitLoop())); + + // "Touch" the file + setTimeStamp(file, s_referenceTimeStamp.addSecs(20)); + + // In stat mode, kdirwatch doesn't notice file changes; we need to trigger it + // by creating a file. + //createTestFile(m_tempDir->path() + "/toplevelfile_5"); + KDirWatch::self()->setDirty(m_tempDir->path()); + + // Wait for KDirWatch to notify the change (especially when using Stat) + enterLoop(); + + // If we come here, then dataChanged() was emitted - all good. +#if 0 + QCOMPARE(modelSpy.count(), 1); + const QVariantList dataChanged = modelSpy.first(); +#else + const QVariantList dataChanged = spyDataChanged[0]; +#endif + QModelIndex receivedIndex = dataChanged[0].value(); + COMPARE_INDEXES(receivedIndex, m_secondFileIndex); + receivedIndex = dataChanged[1].value(); + QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1 + + disconnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + &m_eventLoop, SLOT(exitLoop())); +} + +void KDirModelTest::testRenameFile() +{ + const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); + const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2_renamed"); + + QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + checkedConnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + &m_eventLoop, SLOT(exitLoop())); + + KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged + enterLoop(); + + // If we come here, then dataChanged() was emitted - all good. + QCOMPARE(spyDataChanged.count(), 1); + COMPARE_INDEXES(spyDataChanged[0][0].value(), m_secondFileIndex); + QModelIndex receivedIndex = spyDataChanged[0][1].value(); + QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1 + + // check renaming happened + QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), newUrl.toString()); + + // check that KDirLister::cachedItemForUrl won't give a bad name if copying that item (#195385) + KFileItem cachedItem = KDirLister::cachedItemForUrl(newUrl); + QVERIFY(!cachedItem.isNull()); + QCOMPARE(cachedItem.name(), QString("toplevelfile_2_renamed")); + QCOMPARE(cachedItem.entry().stringValue(KIO::UDSEntry::UDS_NAME), QString("toplevelfile_2_renamed")); + + // Put things back to normal + job = KIO::rename(newUrl, url, KIO::HideProgressInfo); + QVERIFY(job->exec()); + // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged + enterLoop(); + QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); + + disconnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + &m_eventLoop, SLOT(exitLoop())); +} + +void KDirModelTest::testMoveDirectory() +{ + testMoveDirectory(QStringLiteral("subdir")); +} + +void KDirModelTest::testMoveDirectory(const QString &dir /*just a dir name, no slash*/) +{ + const QString path = m_tempDir->path() + '/'; + const QString srcdir = path + dir; + QVERIFY(QDir(srcdir).exists()); + QTemporaryDir destDir; + const QString dest = destDir.path() + '/'; + QVERIFY(QDir(dest).exists()); + + checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + // Move + qDebug() << "Moving" << srcdir << "to" << dest; + KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(srcdir), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + QVERIFY(job->exec()); + + // wait for kdirnotify + enterLoop(); + + disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")).isValid()); + QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); + + checkedConnect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + // Move back + qDebug() << "Moving" << dest + dir << "back to" << srcdir; + job = KIO::move(QUrl::fromLocalFile(dest + dir), QUrl::fromLocalFile(srcdir), KIO::HideProgressInfo); + job->setUiDelegate(0); + job->setUiDelegateExtension(0); + QVERIFY(job->exec()); + + enterLoop(); + + QVERIFY(QDir(srcdir).exists()); + disconnect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + // m_dirIndex is invalid after the above... + fillModel(true); +} + +void KDirModelTest::testRenameDirectory() // #172945, #174703, (and #180156) +{ + const QString path = m_tempDir->path() + '/'; + const QUrl url = QUrl::fromLocalFile(path + "subdir"); + const QUrl newUrl = QUrl::fromLocalFile(path + "subdir_renamed"); + + // For #180156 we need a second kdirmodel, viewing the subdir being renamed. + // I'm abusing m_dirModelForExpand for that purpose. + delete m_dirModelForExpand; + m_dirModelForExpand = new KDirModel; + KDirLister *dirListerForExpand = m_dirModelForExpand->dirLister(); + checkedConnect(dirListerForExpand, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + dirListerForExpand->openUrl(url); // async + enterLoop(); + + // Now do the renaming + QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + checkedConnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + &m_eventLoop, SLOT(exitLoop())); + KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged + enterLoop(); + + // If we come here, then dataChanged() was emitted - all good. + //QCOMPARE(spyDataChanged.count(), 1); // it was in fact emitted 5 times... + //COMPARE_INDEXES(spyDataChanged[0][0].value(), m_dirIndex); + //QModelIndex receivedIndex = spyDataChanged[0][1].value(); + //QCOMPARE(receivedIndex.row(), m_dirIndex.row()); // only compare row; column is count-1 + + // check renaming happened + QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), newUrl.toString()); + qDebug() << newUrl << "indexForUrl=" << m_dirModel->indexForUrl(newUrl) << "m_dirIndex=" << m_dirIndex; + QCOMPARE(m_dirModel->indexForUrl(newUrl), m_dirIndex); + QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); + QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/testfile")).isValid()); + QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir")).isValid()); + QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir/testfile")).isValid()); + + // Check the other kdirmodel got redirected + QCOMPARE(dirListerForExpand->url().toLocalFile(), QString(path + "subdir_renamed")); + + qDebug() << "calling testMoveDirectory(subdir_renamed)"; + + // Test moving the renamed directory; if something inside KDirModel + // wasn't properly updated by the renaming, this would detect it and crash (#180673) + testMoveDirectory(QStringLiteral("subdir_renamed")); + + // Put things back to normal + job = KIO::rename(newUrl, url, KIO::HideProgressInfo); + QVERIFY(job->exec()); + // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged + enterLoop(); + QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), url.toString()); + + disconnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + &m_eventLoop, SLOT(exitLoop())); + + QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), url.toString()); + QCOMPARE(m_dirModel->indexForUrl(url), m_dirIndex); + QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")).isValid()); + QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/testfile")).isValid()); + QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")).isValid()); + QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir/testfile")).isValid()); + QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); + QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/testfile")).isValid()); + QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir")).isValid()); + QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir/testfile")).isValid()); + + // TODO INVESTIGATE + // QCOMPARE(dirListerForExpand->url().toLocalFile(), path+"subdir"); + + delete m_dirModelForExpand; + m_dirModelForExpand = 0; +} + +void KDirModelTest::testRenameDirectoryInCache() // #188807 +{ + // Ensure the stuff is in cache. + fillModel(true); + const QString path = m_tempDir->path() + '/'; + QVERIFY(!m_dirModel->dirLister()->findByUrl(QUrl::fromLocalFile(path)).isNull()); + + // No more dirmodel nor dirlister. + delete m_dirModel; + m_dirModel = 0; + + // Now let's rename a directory that is in KCoreDirListerCache + const QUrl url = QUrl::fromLocalFile(path); + QUrl newUrl = url.adjusted(QUrl::StripTrailingSlash); + newUrl.setPath(newUrl.path() + "_renamed"); + qDebug() << newUrl; + KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + // Put things back to normal + job = KIO::rename(newUrl, url, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + // KDirNotify emits FileRenamed for each rename() above, which in turn + // re-lists the directory. We need to wait for both signals to be emitted + // otherwise the dirlister will not be in the state we expect. + QTest::qWait(200); + + fillModel(true); + + QVERIFY(m_dirIndex.isValid()); + KFileItem rootItem = m_dirModel->dirLister()->findByUrl(QUrl::fromLocalFile(path)); + QVERIFY(!rootItem.isNull()); +} + +void KDirModelTest::testChmodDirectory() // #53397 +{ + QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + checkedConnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + &m_eventLoop, SLOT(exitLoop())); + const QString path = m_tempDir->path() + '/'; + KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); + const mode_t origPerm = rootItem.permissions(); + mode_t newPerm = origPerm ^ S_IWGRP; + //const QFile::Permissions origPerm = rootItem.filePermissions(); + //QVERIFY(origPerm & QFile::ReadOwner); + //const QFile::Permissions newPerm = origPerm ^ QFile::WriteGroup; + QVERIFY(newPerm != origPerm); + KFileItemList items; items << rootItem; + KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QString(), QString(), false, KIO::HideProgressInfo); + job->setUiDelegate(0); + QVERIFY(job->exec()); + // ChmodJob doesn't talk to KDirNotify, kpropertiesdialog does. + // [this allows to group notifications after all the changes one can make in the dialog] + org::kde::KDirNotify::emitFilesChanged(QList() << QUrl::fromLocalFile(path)); + // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved + enterLoop(); + + // If we come here, then dataChanged() was emitted - all good. + QCOMPARE(spyDataChanged.count(), 1); + QModelIndex receivedIndex = spyDataChanged[0][0].value(); + qDebug() << receivedIndex; + QVERIFY(!receivedIndex.isValid()); + + const KFileItem newRootItem = m_dirModel->itemForIndex(QModelIndex()); + QVERIFY(!newRootItem.isNull()); + QCOMPARE(QString::number(newRootItem.permissions(), 16), QString::number(newPerm, 16)); + + disconnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + &m_eventLoop, SLOT(exitLoop())); +} + +enum { + NoFlag = 0, + NewDir = 1, // whether to re-create a new QTemporaryDir completely, to avoid cached fileitems + ListFinalDir = 2, // whether to list the target dir at the same time, like k3b, for #193364 + Recreate = 4, + CacheSubdir = 8, // put subdir in the cache before expandToUrl + // flags, next item is 16! +}; + +void KDirModelTest::testExpandToUrl_data() +{ + QTest::addColumn("flags"); // see enum above + QTest::addColumn("expandToPath"); // relative path + QTest::addColumn("expectedExpandSignals"); + + QTest::newRow("the root, nothing to do") + << int(NoFlag) << QString() << QStringList(); + QTest::newRow(".") + << int(NoFlag) << "." << (QStringList()); + QTest::newRow("subdir") + << int(NoFlag) << "subdir" << (QStringList() << QStringLiteral("subdir")); + QTest::newRow("subdir/.") + << int(NoFlag) << "subdir/." << (QStringList() << QStringLiteral("subdir")); + + const QString subsubdir = QStringLiteral("subdir/subsubdir"); + // Must list root, emit expand for subdir, list subdir, emit expand for subsubdir. + QTest::newRow("subdir/subsubdir") + << int(NoFlag) << subsubdir << (QStringList() << QStringLiteral("subdir") << subsubdir); + + // Must list root, emit expand for subdir, list subdir, emit expand for subsubdir, list subsubdir. + const QString subsubdirfile = subsubdir + "/testfile"; + QTest::newRow("subdir/subsubdir/testfile sync") + << int(NoFlag) << subsubdirfile << (QStringList() << QStringLiteral("subdir") << subsubdir << subsubdirfile); + +#ifndef Q_OS_WIN + // Expand a symlink to a directory (#219547) + const QString dirlink = m_tempDir->path() + "/dirlink"; + createTestSymlink(dirlink, "/"); + QTest::newRow("dirlink") + << int(NoFlag) << "dirlink/tmp" << (QStringList() << QStringLiteral("dirlink") << QStringLiteral("dirlink/tmp")); +#endif + + // Do a cold-cache test too, but nowadays it doesn't change anything anymore, + // apart from testing different code paths inside KDirLister. + QTest::newRow("subdir/subsubdir/testfile with reload") + << int(NewDir) << subsubdirfile << (QStringList() << QStringLiteral("subdir") << subsubdir << subsubdirfile); + + QTest::newRow("hold dest dir") // #193364 + << int(NewDir | ListFinalDir) << subsubdirfile << (QStringList() << QStringLiteral("subdir") << subsubdir << subsubdirfile); + + // Put subdir in cache too (#175035) + QTest::newRow("hold subdir and dest dir") + << int(NewDir | CacheSubdir | ListFinalDir | Recreate) << subsubdirfile + << (QStringList() << QStringLiteral("subdir") << subsubdir << subsubdirfile); + + // Make sure the last test has the Recreate option set, for the subsequent test methods. +} + +void KDirModelTest::testExpandToUrl() +{ + QFETCH(int, flags); + QFETCH(QString, expandToPath); // relative + QFETCH(QStringList, expectedExpandSignals); + + if (flags & NewDir) { + recreateTestData(); + // WARNING! m_dirIndex, m_fileIndex, m_secondFileIndex etc. are not valid anymore after this point! + + } + + const QString path = m_tempDir->path() + '/'; + if (flags & CacheSubdir) { + // This way, the listDir for subdir will find items in cache, and will schedule a CachedItemsJob + m_dirModel->dirLister()->openUrl(QUrl::fromLocalFile(path + "subdir")); + QSignalSpy completedSpy(m_dirModel->dirLister(), SIGNAL(completed())); + QVERIFY(completedSpy.wait(2000)); + } + if (flags & ListFinalDir) { + // This way, the last listDir will find items in cache, and will schedule a CachedItemsJob + m_dirModel->dirLister()->openUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); + QSignalSpy completedSpy(m_dirModel->dirLister(), SIGNAL(completed())); + QVERIFY(completedSpy.wait(2000)); + } + + if (!m_dirModelForExpand || (flags & NewDir)) { + delete m_dirModelForExpand; + m_dirModelForExpand = new KDirModel; + checkedConnect(m_dirModelForExpand, SIGNAL(expand(QModelIndex)), + this, SLOT(slotExpand(QModelIndex))); + checkedConnect(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(slotRowsInserted(QModelIndex,int,int))); + KDirLister *dirListerForExpand = m_dirModelForExpand->dirLister(); + dirListerForExpand->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // async + } + m_rowsInsertedEmitted = false; + m_expectedExpandSignals = expectedExpandSignals; + m_nextExpectedExpandSignals = 0; + QSignalSpy spyExpand(m_dirModelForExpand, SIGNAL(expand(QModelIndex))); + m_urlToExpandTo = QUrl::fromLocalFile(path + expandToPath); + // If KDirModel doesn't know this URL yet, then we want to see rowsInserted signals + // being emitted, so that the slots can get the index to that url then. + m_expectRowsInserted = !expandToPath.isEmpty() && !m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid(); + m_dirModelForExpand->expandToUrl(m_urlToExpandTo); + if (expectedExpandSignals.isEmpty()) { + QTest::qWait(20); // to make sure we process queued connection calls, otherwise spyExpand.count() is always 0 even if there's a bug... + QCOMPARE(spyExpand.count(), 0); + } else { + if (spyExpand.count() < expectedExpandSignals.count()) { + enterLoop(); + QCOMPARE(spyExpand.count(), expectedExpandSignals.count()); + } + if (m_expectRowsInserted) { + QVERIFY(m_rowsInsertedEmitted); + } + } + + // Now it should exist + if (!expandToPath.isEmpty() && expandToPath != QLatin1String(".")) { + qDebug() << "Do I know" << m_urlToExpandTo << "?"; + QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid()); + } + + if (flags & ListFinalDir) { + testUpdateParentAfterExpand(); + } + + if (flags & Recreate) { + // Clean up, for the next tests + recreateTestData(); + fillModel(false); + } +} + +void KDirModelTest::slotExpand(const QModelIndex &index) +{ + QVERIFY(index.isValid()); + const QString path = m_tempDir->path() + '/'; + KFileItem item = m_dirModelForExpand->itemForIndex(index); + QVERIFY(!item.isNull()); + qDebug() << item.url().toLocalFile(); + QCOMPARE(item.url().toLocalFile(), QString(path + m_expectedExpandSignals[m_nextExpectedExpandSignals++])); + + // if rowsInserted wasn't emitted yet, then any proxy model would be unable to do anything with index at this point + if (item.url() == m_urlToExpandTo) { + QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid()); + if (m_expectRowsInserted) { + QVERIFY(m_rowsInsertedEmitted); + } + } + + if (m_nextExpectedExpandSignals == m_expectedExpandSignals.count()) { + m_eventLoop.exitLoop(); // done + } +} + +void KDirModelTest::slotRowsInserted(const QModelIndex &, int, int) +{ + m_rowsInsertedEmitted = true; +} + +// This code is called by testExpandToUrl +void KDirModelTest::testUpdateParentAfterExpand() // #193364 +{ + const QString path = m_tempDir->path() + '/'; + const QString file = path + "subdir/aNewFile"; + qDebug() << "Creating" << file; + QVERIFY(!QFile::exists(file)); + createTestFile(file); + QSignalSpy spyRowsInserted(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int))); + QVERIFY(spyRowsInserted.wait(1000)); +} + +void KDirModelTest::testFilter() +{ + QVERIFY(m_dirIndex.isValid()); + const int oldTopLevelRowCount = m_dirModel->rowCount(); + const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); + QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), SIGNAL(itemsFilteredByMime(KFileItemList))); + QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), SIGNAL(itemsDeleted(KFileItemList))); + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel*")); + QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet + QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet + m_dirModel->dirLister()->emitChanges(); + + QCOMPARE(m_dirModel->rowCount(), 4); // 3 toplevel* files, one subdir + QCOMPARE(m_dirModel->rowCount(m_dirIndex), 1); // the files get filtered out, the subdir remains + + // In the subdir, we can get rowsRemoved signals like (1,2) or (0,0)+(2,2), + // depending on the order of the files in the model. + // So QCOMPARE(spyRowsRemoved.count(), 3) is fragile, we rather need + // to sum up the removed rows per parent directory. + QMap rowsRemovedPerDir; + for (int i = 0; i < spyRowsRemoved.count(); ++i) { + const QVariantList args = spyRowsRemoved[i]; + const QModelIndex parentIdx = args[0].value(); + QString dirName; + if (parentIdx.isValid()) { + const KFileItem item = m_dirModel->itemForIndex(parentIdx); + dirName = item.name(); + } else { + dirName = QStringLiteral("root"); + } + rowsRemovedPerDir[dirName] += args[2].toInt() - args[1].toInt() + 1; + //qDebug() << parentIdx << args[1].toInt() << args[2].toInt(); + } + QCOMPARE(rowsRemovedPerDir.count(), 3); // once for every dir + QCOMPARE(rowsRemovedPerDir.value("root"), 1); // one from toplevel ('special chars') + QCOMPARE(rowsRemovedPerDir.value("subdir"), 2); // two from subdir + QCOMPARE(rowsRemovedPerDir.value("subsubdir"), 1); // one from subsubdir + QCOMPARE(spyItemsDeleted.count(), 3); // once for every dir + QCOMPARE(spyItemsDeleted[0][0].value().count(), 1); // one from toplevel ('special chars') + QCOMPARE(spyItemsDeleted[1][0].value().count(), 2); // two from subdir + QCOMPARE(spyItemsDeleted[2][0].value().count(), 1); // one from subsubdir + QCOMPARE(spyItemsFilteredByMime.count(), 0); + spyItemsDeleted.clear(); + spyItemsFilteredByMime.clear(); + + // Reset the filter + qDebug() << "reset to no filter"; + m_dirModel->dirLister()->setNameFilter(QString()); + m_dirModel->dirLister()->emitChanges(); + + QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); + QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); + QCOMPARE(spyItemsDeleted.count(), 0); + QCOMPARE(spyItemsFilteredByMime.count(), 0); + + // The order of things changed because of filtering. + // Fill again, so that m_fileIndex etc. are correct again. + fillModel(true); +} + +void KDirModelTest::testMimeFilter() +{ + QVERIFY(m_dirIndex.isValid()); + const int oldTopLevelRowCount = m_dirModel->rowCount(); + const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); + QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), SIGNAL(itemsFilteredByMime(KFileItemList))); + QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), SIGNAL(itemsDeleted(KFileItemList))); + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + m_dirModel->dirLister()->setMimeFilter(QStringList() << QStringLiteral("application/pdf")); + QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet + QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet + m_dirModel->dirLister()->emitChanges(); + + QCOMPARE(m_dirModel->rowCount(), 1); // 1 pdf files, no subdir anymore + + QVERIFY(spyRowsRemoved.count() >= 1); // depends on contiguity... + QVERIFY(spyItemsDeleted.count() >= 1); // once for every dir + // Maybe it would make sense to have those items in itemsFilteredByMime, + // but well, for the only existing use of that signal (mime filter plugin), + // it's not really necessary, the plugin has seen those files before anyway. + // The signal is mostly useful for the case of listing a dir with a mime filter set. + //QCOMPARE(spyItemsFilteredByMime.count(), 1); + //QCOMPARE(spyItemsFilteredByMime[0][0].value().count(), 4); + spyItemsDeleted.clear(); + spyItemsFilteredByMime.clear(); + + // Reset the filter + qDebug() << "reset to no filter"; + m_dirModel->dirLister()->setMimeFilter(QStringList()); + m_dirModel->dirLister()->emitChanges(); + + QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); + QCOMPARE(spyItemsDeleted.count(), 0); + QCOMPARE(spyItemsFilteredByMime.count(), 0); + + // The order of things changed because of filtering. + // Fill again, so that m_fileIndex etc. are correct again. + fillModel(true); +} + +void KDirModelTest::testShowHiddenFiles() // #174788 +{ + KDirLister *dirLister = m_dirModel->dirLister(); + + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + QSignalSpy spyNewItems(dirLister, SIGNAL(newItems(KFileItemList))); + QSignalSpy spyRowsInserted(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int))); + dirLister->setShowingDotFiles(true); + dirLister->emitChanges(); + const int numberOfDotFiles = 2; + QCOMPARE(spyNewItems.count(), 1); + QCOMPARE(spyNewItems[0][0].value().count(), numberOfDotFiles); + QCOMPARE(spyRowsInserted.count(), 1); + QCOMPARE(spyRowsRemoved.count(), 0); + spyNewItems.clear(); + spyRowsInserted.clear(); + + dirLister->setShowingDotFiles(false); + dirLister->emitChanges(); + QCOMPARE(spyNewItems.count(), 0); + QCOMPARE(spyRowsInserted.count(), 0); + QCOMPARE(spyRowsRemoved.count(), 1); +} + +void KDirModelTest::testMultipleSlashes() +{ + const QString path = m_tempDir->path() + '/'; + + QModelIndex index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//testfile")); + QVERIFY(index.isValid()); + + index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//subsubdir//")); + QVERIFY(index.isValid()); + + index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir///subsubdir////testfile")); + QVERIFY(index.isValid()); +} + +void KDirModelTest::testUrlWithRef() // #171117 +{ + const QString path = m_tempDir->path() + '/'; + KDirLister *dirLister = m_dirModel->dirLister(); + QUrl url = QUrl::fromLocalFile(path); + url.setFragment(QStringLiteral("ref")); + QVERIFY(url.url().endsWith("#ref")); + dirLister->openUrl(url, KDirLister::NoFlags); + checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + enterLoop(); + + QCOMPARE(dirLister->url().toString(), url.toString(QUrl::StripTrailingSlash)); + collectKnownIndexes(); + disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); +} + +void KDirModelTest::testFontUrlWithHost() // #160057 +{ + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("fonts"))) { + QSKIP("kio_fonts not installed"); + } + QUrl url(QStringLiteral("fonts://foo/System")); + KDirLister *dirLister = m_dirModel->dirLister(); + dirLister->openUrl(url, KDirLister::NoFlags); + checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + enterLoop(); + + QCOMPARE(dirLister->url().toString(), QString("fonts:/System")); +} + +void KDirModelTest::testRemoteUrlWithHost() // #178416 +{ + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("remote"))) { + QSKIP("kio_remote not installed"); + } + QUrl url(QStringLiteral("remote://foo")); + KDirLister *dirLister = m_dirModel->dirLister(); + dirLister->openUrl(url, KDirLister::NoFlags); + checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + enterLoop(); + + QCOMPARE(dirLister->url().toString(), QString("remote:")); +} + +void KDirModelTest::testZipFile() // # 171721 +{ + const QString path = QFileInfo(QFINDTESTDATA("wronglocalsizes.zip")).absolutePath(); + KDirLister *dirLister = m_dirModel->dirLister(); + dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + enterLoop(); + disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + + QUrl zipUrl(QUrl::fromLocalFile(path)); + zipUrl.setPath(zipUrl.path() + "/wronglocalsizes.zip"); // just a zip file lying here for other reasons + + QVERIFY(QFile::exists(zipUrl.toLocalFile())); + zipUrl.setScheme(QStringLiteral("zip")); + QModelIndex index = m_dirModel->indexForUrl(zipUrl); + QVERIFY(!index.isValid()); // protocol mismatch, can't find it! + zipUrl.setScheme(QStringLiteral("file")); + index = m_dirModel->indexForUrl(zipUrl); + QVERIFY(index.isValid()); +} + +void KDirModelTest::testSmb() +{ + const QUrl smbUrl(QStringLiteral("smb:/")); + // TODO: feed a KDirModel without using a KDirLister. + // Calling the slots directly. + // This requires that KDirModel does not ask the KDirLister for its rootItem anymore, + // but that KDirLister emits the root item whenever it changes. + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("smb"))) { + QSKIP("kio_smb not installed"); + } + KDirLister *dirLister = m_dirModel->dirLister(); + dirLister->openUrl(smbUrl, KDirLister::NoFlags); + checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + checkedConnect(dirLister, SIGNAL(canceled()), this, SLOT(slotListingCompleted())); + QSignalSpy spyCanceled(dirLister, SIGNAL(canceled())); + enterLoop(); // wait for completed signal + + if (spyCanceled.count() > 0) { + QSKIP("smb:/ returns an error, probably no network available"); + } + + QModelIndex index = m_dirModel->index(0, 0); + if (index.isValid()) { + QVERIFY(m_dirModel->canFetchMore(index)); + m_dirModel->fetchMore(index); + enterLoop(); // wait for completed signal + disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + } +} + +class MyDirLister : public KDirLister +{ +public: + void emitItemsDeleted(const KFileItemList &items) + { + emit itemsDeleted(items); + } +}; + +void KDirModelTest::testBug196695() +{ + KFileItem rootItem(QUrl::fromLocalFile(m_tempDir->path()), QString(), KFileItem::Unknown); + KFileItem childItem(QUrl::fromLocalFile(QString(m_tempDir->path() + "/toplevelfile_1")), QString(), KFileItem::Unknown); + + KFileItemList list; + // Important: the root item must not be first in the list to trigger bug 196695 + list << childItem << rootItem; + + MyDirLister *dirLister = static_cast(m_dirModel->dirLister()); + dirLister->emitItemsDeleted(list); + + fillModel(true); +} + +void KDirModelTest::testMimeData() +{ + QModelIndex index0 = m_dirModel->index(0, 0); + QVERIFY(index0.isValid()); + QModelIndex index1 = m_dirModel->index(1, 0); + QVERIFY(index1.isValid()); + QList indexes; + indexes << index0 << index1; + QMimeData *mimeData = m_dirModel->mimeData(indexes); + QVERIFY(mimeData); + QVERIFY(mimeData->hasUrls()); + const QList urls = mimeData->urls(); + QCOMPARE(urls.count(), indexes.count()); +} + +void KDirModelTest::testDotHiddenFile_data() +{ + QTest::addColumn("fileContents"); + QTest::addColumn("expectedListing"); + + QStringList allItems; allItems << QStringLiteral("toplevelfile_1") << QStringLiteral("toplevelfile_2") << QStringLiteral("toplevelfile_3") << SPECIALCHARS << QStringLiteral("subdir"); + QTest::newRow("empty_file") << QStringList() << allItems; + + QTest::newRow("simple_name") << (QStringList() << QStringLiteral("toplevelfile_1")) << QStringList(allItems.mid(1)); + + QStringList allButSpecialChars = allItems; allButSpecialChars.removeAt(3); + QTest::newRow("special_chars") << (QStringList() << SPECIALCHARS) << allButSpecialChars; + + QStringList allButSubdir = allItems; allButSubdir.removeAt(4); + QTest::newRow("subdir") << (QStringList() << QStringLiteral("subdir")) << allButSubdir; + + QTest::newRow("many_lines") << (QStringList() << QStringLiteral("subdir") << QStringLiteral("toplevelfile_1") << QStringLiteral("toplevelfile_3") << QStringLiteral("toplevelfile_2")) + << (QStringList() << SPECIALCHARS); +} + +void KDirModelTest::testDotHiddenFile() +{ + QFETCH(QStringList, fileContents); + QFETCH(QStringList, expectedListing); + + const QString path = m_tempDir->path() + '/'; + const QString dotHiddenFile = path + ".hidden"; + QTest::qWait(1000); // mtime-based cache, so we need to wait for 1 second + QFile dh(dotHiddenFile); + QVERIFY(dh.open(QIODevice::WriteOnly)); + dh.write(fileContents.join('\n').toUtf8()); + dh.close(); + + // Do it twice: once to read from the file and once to use the cache + for (int i = 0; i < 2; ++i) { + fillModel(true, false); + QStringList files; + for (int row = 0; row < m_dirModel->rowCount(); ++row) { + files.append(m_dirModel->index(row, KDirModel::Name).data().toString()); + } + files.sort(); + expectedListing.sort(); + QCOMPARE(files, expectedListing); + } + + dh.remove(); +} + +void KDirModelTest::testDeleteFile() +{ + fillModel(true); + + QVERIFY(m_fileIndex.isValid()); + const int oldTopLevelRowCount = m_dirModel->rowCount(); + const QString path = m_tempDir->path() + '/'; + const QString file = path + "toplevelfile_1"; + const QUrl url = QUrl::fromLocalFile(file); + + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved + enterLoop(); + + // If we come here, then rowsRemoved() was emitted - all good. + const int topLevelRowCount = m_dirModel->rowCount(); + QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before + QCOMPARE(spyRowsRemoved.count(), 1); + QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); + QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); + disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); + QVERIFY(!fileIndex.isValid()); + + // Recreate the file, for consistency in the next tests + // So the second part of this test is a "testCreateFile" + createTestFile(file); + // Tricky problem - KDirLister::openUrl will emit items from cache + // and then schedule an update; so just calling fillModel would + // not wait enough, it would abort due to not finding toplevelfile_1 + // in the items from cache. This progressive-emitting behavior is fine + // for GUIs but not for unit tests ;-) + fillModel(true, false); + fillModel(false); +} + +void KDirModelTest::testDeleteFileWhileListing() // doesn't really test that yet, the kdirwatch deleted signal comes too late +{ + const int oldTopLevelRowCount = m_dirModel->rowCount(); + const QString path = m_tempDir->path() + '/'; + const QString file = path + "toplevelfile_1"; + const QUrl url = QUrl::fromLocalFile(file); + + KDirLister *dirLister = m_dirModel->dirLister(); + QSignalSpy spyCompleted(dirLister, SIGNAL(completed())); + checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); + dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); + if (!spyCompleted.isEmpty()) { + QSKIP("listing completed too early"); + } + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + if (spyCompleted.isEmpty()) { + enterLoop(); + } + QVERIFY(spyRowsRemoved.wait(1000)); + + const int topLevelRowCount = m_dirModel->rowCount(); + QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before + QCOMPARE(spyRowsRemoved.count(), 1); + QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); + QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); + + QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); + QVERIFY(!fileIndex.isValid()); + + qDebug() << "Test done, recreating file"; + + // Recreate the file, for consistency in the next tests + // So the second part of this test is a "testCreateFile" + createTestFile(file); + fillModel(true, false); // see testDeleteFile + fillModel(false); +} + +void KDirModelTest::testOverwriteFileWithDir() // #151851 c4 +{ + fillModel(false); + const QString path = m_tempDir->path() + '/'; + const QString dir = path + "subdir"; + const QString file = path + "toplevelfile_1"; + const int oldTopLevelRowCount = m_dirModel->rowCount(); + + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + KIO::Job *job = KIO::move(QUrl::fromLocalFile(dir), QUrl::fromLocalFile(file), KIO::HideProgressInfo); + job->setUiDelegate(0); + PredefinedAnswerJobUiDelegate extension; + extension.m_renameResult = KIO::R_OVERWRITE; + job->setUiDelegateExtension(&extension); + QVERIFY(job->exec()); + + QCOMPARE(extension.m_askFileRenameCalled, 1); + + // Wait for a removal within the top level (that's for the old file going away), and also + // for a dataChanged which notifies us that a file has become a directory + bool removalWithinTopLevel = false; + bool dataChangedAtFirstLevel = false; + int retries = 0; + while ((!removalWithinTopLevel || !dataChangedAtFirstLevel) && retries < 100) { + for (int i = 0; i < spyRowsRemoved.size() && !removalWithinTopLevel; ++i) { + QModelIndex parent = spyRowsRemoved[i][0].value(); + if (!parent.isValid()) { + // yes, that's what we have been waiting for + removalWithinTopLevel = true; + } + } + for (int i = 0; i < spyDataChanged.size() && !dataChangedAtFirstLevel; ++i) { + QModelIndex idx = spyDataChanged[i][0].value(); + if (idx.isValid() && !idx.parent().isValid()) { + // a change of a node whose parent is root, yay, that's it + dataChangedAtFirstLevel = true; + } + } + QTest::qWait(10); + ++retries; + } + QVERIFY(removalWithinTopLevel); + QVERIFY(dataChangedAtFirstLevel); + + // If we come here, then rowsRemoved() was emitted - all good. + const int topLevelRowCount = m_dirModel->rowCount(); + QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before + + QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(dir)).isValid()); + QModelIndex newIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); + QVERIFY(newIndex.isValid()); + KFileItem newItem = m_dirModel->itemForIndex(newIndex); + QVERIFY(newItem.isDir()); // yes, the file is a dir now ;-) + + qDebug() << "========= Test done, recreating test data ========="; + + recreateTestData(); + fillModel(false); +} + +void KDirModelTest::testDeleteFiles() +{ + const int oldTopLevelRowCount = m_dirModel->rowCount(); + const QString file = m_tempDir->path() + "/toplevelfile_"; + QList urls; + urls << QUrl::fromLocalFile(file + '1') << QUrl::fromLocalFile(file + '2') << QUrl::fromLocalFile(file + '3'); + + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + KIO::DeleteJob *job = KIO::del(urls, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + int numRowsRemoved = 0; + while (numRowsRemoved < 3) { + + QTest::qWait(20); + + numRowsRemoved = 0; + for (int sigNum = 0; sigNum < spyRowsRemoved.count(); ++sigNum) { + numRowsRemoved += spyRowsRemoved[sigNum][2].toInt() - spyRowsRemoved[sigNum][1].toInt() + 1; + } + qDebug() << "numRowsRemoved=" << numRowsRemoved; + } + + const int topLevelRowCount = m_dirModel->rowCount(); + QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 3); // three less than before + + qDebug() << "Recreating test data"; + recreateTestData(); + qDebug() << "Re-filling model"; + fillModel(false); +} + +// A renaming that looks more like a deletion to the model +void KDirModelTest::testRenameFileToHidden() // #174721 +{ + const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); + const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/.toplevelfile_2"); + + QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + QSignalSpy spyRowsInserted(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int))); + checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister + enterLoop(); + + // If we come here, then rowsRemoved() was emitted - all good. + QCOMPARE(spyDataChanged.count(), 0); + QCOMPARE(spyRowsRemoved.count(), 1); + QCOMPARE(spyRowsInserted.count(), 0); + COMPARE_INDEXES(spyRowsRemoved[0][0].value(), QModelIndex()); // parent is invalid + const int row = spyRowsRemoved[0][1].toInt(); + QCOMPARE(row, m_secondFileIndex.row()); // only compare row + + disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + spyRowsRemoved.clear(); + + // Put things back to normal, should make the file reappear + checkedConnect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + job = KIO::rename(newUrl, url, KIO::HideProgressInfo); + QVERIFY(job->exec()); + // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister + enterLoop(); + QCOMPARE(spyDataChanged.count(), 0); + QCOMPARE(spyRowsRemoved.count(), 0); + QCOMPARE(spyRowsInserted.count(), 1); + int newRow = spyRowsInserted[0][1].toInt(); + m_secondFileIndex = m_dirModel->index(newRow, 0); + QVERIFY(m_secondFileIndex.isValid()); + QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); +} + +void KDirModelTest::testDeleteDirectory() +{ + const QString path = m_tempDir->path() + '/'; + const QUrl url = QUrl::fromLocalFile(path + "subdir/subsubdir"); + + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + QSignalSpy spyDirWatchDeleted(KDirWatch::self(), SIGNAL(deleted(QString))); + + KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved + enterLoop(); + + // If we come here, then rowsRemoved() was emitted - all good. + QCOMPARE(spyRowsRemoved.count(), 1); + disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + QModelIndex deletedDirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); + QVERIFY(!deletedDirIndex.isValid()); + QModelIndex dirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")); + QVERIFY(dirIndex.isValid()); + + // TODO!!! Bug in KDirWatch? ### + // QCOMPARE(spyDirWatchDeleted.count(), 1); +} + +void KDirModelTest::testDeleteCurrentDirectory() +{ + const int oldTopLevelRowCount = m_dirModel->rowCount(); + const QString path = m_tempDir->path() + '/'; + const QUrl url = QUrl::fromLocalFile(path); + + QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); + checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + KDirWatch::self()->statistics(); + + KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); + QVERIFY(job->exec()); + + // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved + enterLoop(); + + // If we come here, then rowsRemoved() was emitted - all good. + const int topLevelRowCount = m_dirModel->rowCount(); + QCOMPARE(topLevelRowCount, 0); // empty + + // We can get rowsRemoved for subdirs first, since kdirwatch notices that. + QVERIFY(spyRowsRemoved.count() >= 1); + + // Look for the signal(s) that had QModelIndex() as parent. + int i; + int numDeleted = 0; + for (i = 0; i < spyRowsRemoved.count(); ++i) { + const int from = spyRowsRemoved[i][1].toInt(); + const int to = spyRowsRemoved[i][2].toInt(); + qDebug() << spyRowsRemoved[i][0].value() << from << to; + if (!spyRowsRemoved[i][0].value().isValid()) { + numDeleted += (to - from) + 1; + } + } + + QCOMPARE(numDeleted, oldTopLevelRowCount); + disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &m_eventLoop, SLOT(exitLoop())); + + QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); + QVERIFY(!fileIndex.isValid()); +} + +void KDirModelTest::testQUrlHash() +{ + const int count = 3000; + // Prepare an array of QUrls so that url constructing isn't part of the timing + QVector urls; + urls.resize(count); + for (int i = 0; i < count; ++i) { + urls[i] = QUrl("http://www.kde.org/path/" + QString::number(i)); + } + QHash qurlHash; + QHash kurlHash; + QTime dt; dt.start(); + for (int i = 0; i < count; ++i) { + qurlHash.insert(urls[i], i); + } + //qDebug() << "inserting" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; + dt.start(); + for (int i = 0; i < count; ++i) { + kurlHash.insert(urls[i], i); + } + //qDebug() << "inserting" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; + // Nice results: for count=30000 I got 4515 (before) and 103 (after) + + dt.start(); + for (int i = 0; i < count; ++i) { + QCOMPARE(qurlHash.value(urls[i]), i); + } + //qDebug() << "looking up" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; + dt.start(); + for (int i = 0; i < count; ++i) { + QCOMPARE(kurlHash.value(urls[i]), i); + } + //qDebug() << "looking up" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; + // Nice results: for count=30000 I got 4296 (before) and 63 (after) +} diff --git a/autotests/kdirmodeltest.h b/autotests/kdirmodeltest.h new file mode 100644 index 0000000..2f48b67 --- /dev/null +++ b/autotests/kdirmodeltest.h @@ -0,0 +1,118 @@ +/* This file is part of the KDE project + Copyright (C) 2006 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef KDIRMODELTEST_H +#define KDIRMODELTEST_H + +#include +#include +#include +#include +#include +#include + +// If you disable this, you need to change all exitLoop into quit in connect() statements... +#define USE_QTESTEVENTLOOP + +class KDirModelTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void cleanup(); + void testRowCount(); + void testIndex(); + void testNames(); + void testItemForIndex(); + void testIndexForItem(); + void testData(); + void testReload(); + void testModifyFile(); + void testRenameFile(); + void testMoveDirectory(); + void testRenameDirectory(); + void testRenameDirectoryInCache(); + void testChmodDirectory(); + void testExpandToUrl_data(); + void testExpandToUrl(); + void testFilter(); + void testMimeFilter(); + void testShowHiddenFiles(); + void testMultipleSlashes(); + void testUrlWithRef(); + void testFontUrlWithHost(); + void testRemoteUrlWithHost(); + void testZipFile(); + void testSmb(); + void testBug196695(); + void testMimeData(); + void testDotHiddenFile_data(); + void testDotHiddenFile(); + + // These tests must be done last + void testDeleteFile(); + void testDeleteFileWhileListing(); + void testOverwriteFileWithDir(); + void testDeleteFiles(); + void testRenameFileToHidden(); + void testDeleteDirectory(); + void testDeleteCurrentDirectory(); + + // Somewhat unrelated + void testQUrlHash(); + +protected Q_SLOTS: // 'more private than private slots' - i.e. not seen by qtestlib + void slotListingCompleted(); + void slotExpand(const QModelIndex &index); + void slotRowsInserted(const QModelIndex &index, int, int); + +private: + void recreateTestData(); + void enterLoop(); + void fillModel(bool reload, bool expectAllIndexes = true); + void collectKnownIndexes(); + void testMoveDirectory(const QString &srcdir); + void testUpdateParentAfterExpand(); + +private: +#ifdef USE_QTESTEVENTLOOP + QTestEventLoop m_eventLoop; +#else + QEventLoop m_eventLoop; +#endif + QTemporaryDir *m_tempDir; + KDirModel *m_dirModel; + QModelIndex m_fileIndex; + QModelIndex m_specialFileIndex; + QModelIndex m_secondFileIndex; + QModelIndex m_dirIndex; + QModelIndex m_fileInDirIndex; + QModelIndex m_fileInSubdirIndex; + QStringList m_topLevelFileNames; // files only + + // for slotExpand + QStringList m_expectedExpandSignals; + int m_nextExpectedExpandSignals; // index into m_expectedExpandSignals + KDirModel *m_dirModelForExpand; + QUrl m_urlToExpandTo; + bool m_rowsInsertedEmitted; + bool m_expectRowsInserted; +}; + +#endif diff --git a/autotests/kdiroperatortest.cpp b/autotests/kdiroperatortest.cpp new file mode 100644 index 0000000..c8616e6 --- /dev/null +++ b/autotests/kdiroperatortest.cpp @@ -0,0 +1,123 @@ +/* This file is part of the KDE libraries + Copyright (c) 2009 David Faure + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include + +/** + * Unit test for KDirOperator + */ +class KDirOperatorTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase() + { + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + } + + void cleanupTestCase() + { + } + + void testNoViewConfig() + { + KDirOperator dirOp; + // setIconsZoom tries to write config. + // Make sure it won't crash if setViewConfig() isn't called + dirOp.setIconsZoom(5); + QCOMPARE(dirOp.iconsZoom(), 5); + } + + void testReadConfig() + { + // Test: Make sure readConfig() and then setView() restores + // the correct kind of view. + KDirOperator *dirOp = new KDirOperator; + dirOp->setView(KFile::DetailTree); + dirOp->setShowHiddenFiles(true); + KConfigGroup cg(KSharedConfig::openConfig(), "diroperator"); + dirOp->writeConfig(cg); + delete dirOp; + + dirOp = new KDirOperator; + dirOp->readConfig(cg); + dirOp->setView(KFile::Default); + QVERIFY(dirOp->showHiddenFiles()); + // KDirOperatorDetail inherits QTreeView, so this test should work + QVERIFY(qobject_cast(dirOp->view())); + delete dirOp; + } + + /** + * testBug187066 does the following: + * + * 1. Open a KDirOperator in kdelibs/kfile + * 2. Set the current item to "file:///" + * 3. Set the current item to "file:///.../kdelibs/kfile/tests/kdiroperatortest.cpp" + * + * This may result in a crash, see https://bugs.kde.org/show_bug.cgi?id=187066 + */ + + void testBug187066() + { + const QString dir = QFileInfo(QFINDTESTDATA("kdiroperatortest.cpp")).absolutePath(); + const QUrl kFileDirUrl(QUrl::fromLocalFile(dir).adjusted(QUrl::RemoveFilename)); + + KDirOperator dirOp(kFileDirUrl); + QSignalSpy completedSpy(dirOp.dirLister(), SIGNAL(completed())); + dirOp.setView(KFile::DetailTree); + completedSpy.wait(1000); + dirOp.setCurrentItem(QUrl(QStringLiteral("file:///"))); + dirOp.setCurrentItem(QUrl::fromLocalFile(QFINDTESTDATA("kdiroperatortest.cpp"))); + //completedSpy.wait(1000); + QTest::qWait(1000); + } + + void testSetUrlPathAdjustment_data() + { + QTest::addColumn("url"); + QTest::addColumn("expectedUrl"); + + QTest::newRow("with_host") << QUrl(QStringLiteral("ftp://foo.com/folder")) << QUrl(QStringLiteral("ftp://foo.com/folder/")); + QTest::newRow("with_no_host") << QUrl(QStringLiteral("smb://")) << QUrl(QStringLiteral("smb://")); + QTest::newRow("with_host_without_path") << QUrl(QStringLiteral("ftp://user@example.com")) << QUrl(QStringLiteral("ftp://user@example.com")); + } + + void testSetUrlPathAdjustment() + { + QFETCH(QUrl, url); + QFETCH(QUrl, expectedUrl); + + KDirOperator dirOp; + QSignalSpy spy(&dirOp, SIGNAL(urlEntered(QUrl))); + dirOp.setUrl(url, true); + QCOMPARE(spy.takeFirst().at(0).toUrl(), expectedUrl); + } +}; + +QTEST_MAIN(KDirOperatorTest) + +#include "kdiroperatortest.moc" diff --git a/autotests/kfilecopytomenutest.cpp b/autotests/kfilecopytomenutest.cpp new file mode 100644 index 0000000..05ae070 --- /dev/null +++ b/autotests/kfilecopytomenutest.cpp @@ -0,0 +1,183 @@ +/* This file is part of the KDE project + Copyright (C) 2014 David Faure + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "kiotesthelper.h" +#include "jobuidelegatefactory.h" + +#include + +class KFileCopyToMenuTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase() + { + QStandardPaths::setTestModeEnabled(true); + qputenv("KIOSLAVE_ENABLE_TESTMODE", "1"); // ensure the ioslaves call QStandardPaths::setTestModeEnabled too + qputenv("KDE_FORK_SLAVES", "yes"); // to avoid a runtime dependency on klauncher + + QVERIFY(m_tempDir.isValid()); + QVERIFY(m_tempDestDir.isValid()); + QVERIFY(m_nonWritableTempDir.isValid()); + QVERIFY(QFile(m_nonWritableTempDir.path()).setPermissions(QFile::ReadOwner | QFile::ReadUser | QFile::ExeOwner | QFile::ExeUser)); + m_srcDir = m_tempDir.path(); + m_destDir = m_tempDestDir.path(); + + m_srcFile = m_srcDir + "/srcfile"; + + KIO::setDefaultJobUiDelegateExtension(0); // no "skip" dialogs + + // Set a recent dir + KConfigGroup recentDirsGroup(KSharedConfig::openConfig(), "kuick-copy"); + m_recentDirs + << m_destDir + QStringLiteral("/nonexistentsubdir") // will be action number count-3 + << m_nonWritableTempDir.path() // will be action number count-2 + << m_destDir; // will be action number count-1 + recentDirsGroup.writeEntry("Paths", m_recentDirs); + + m_lastActionCount = 0; + } + + void cleanupTestCase() + { + QVERIFY(QFile(m_nonWritableTempDir.path()).setPermissions(QFile::ReadOwner | QFile::ReadUser | QFile::WriteOwner | QFile::WriteUser | QFile::ExeOwner | QFile::ExeUser)); + } + + // Before every test method, ensure the test file m_srcFile exists + void init() + { + if (QFile::exists(m_srcFile)) { + QVERIFY(QFileInfo(m_srcFile).isWritable()); + } else { + QFile srcFile(m_srcFile); + QVERIFY2(srcFile.open(QFile::WriteOnly), qPrintable(srcFile.errorString())); + srcFile.write("Hello world\n"); + } + QVERIFY(QFileInfo(m_srcFile).isWritable()); + } + + void shouldHaveParentWidget() + { + KFileCopyToMenu generator(&m_parentWidget); + QCOMPARE(generator.parent(), &m_parentWidget); + } + + void shouldAddActions() + { + KFileCopyToMenu generator(&m_parentWidget); + QMenu menu; + generator.addActionsTo(&menu); + QList urls; urls << QUrl::fromLocalFile(m_srcFile); + generator.setUrls(urls); + QCOMPARE(extractActionNames(menu), QStringList() << "copyTo_submenu" << "moveTo_submenu"); + //menu.popup(QPoint(-50, -50)); + QMenu *copyMenu = menu.actions().at(0)->menu(); // "copy" submenu + QVERIFY(copyMenu); + + // When + copyMenu->popup(QPoint(-100, -100)); + + // Then + const QStringList actionNames = extractActionNames(*copyMenu); + QCOMPARE(actionNames.first(), QString("home")); + QVERIFY(actionNames.contains("browse")); + QCOMPARE(actionNames.at(actionNames.count() - 2), m_nonWritableTempDir.path()); + QCOMPARE(actionNames.last(), m_destDir); + } + + void shouldTryCopyingToRecentPath_data() + { + QTest::addColumn("actionNumber"); // from the bottom of the menu, starting at 1; see the recentDirs list in initTestCase + QTest::addColumn("expectedErrorCode"); + + QTest::newRow("working") << 1 << 0; // no error + QTest::newRow("non_writable") << 2 << int(KIO::ERR_WRITE_ACCESS_DENIED); + QTest::newRow("non_existing") << 3 << int(KIO::ERR_CANNOT_OPEN_FOR_WRITING); + } + + void shouldTryCopyingToRecentPath() + { + QFETCH(int, actionNumber); + QFETCH(int, expectedErrorCode); + + KFileCopyToMenu generator(&m_parentWidget); + QMenu menu; + QList urls; urls << QUrl::fromLocalFile(m_srcFile); + generator.setUrls(urls); + generator.addActionsTo(&menu); + QMenu *copyMenu = menu.actions().at(0)->menu(); + copyMenu->popup(QPoint(-100, -100)); + const QList actions = copyMenu->actions(); + if (m_lastActionCount == 0) { + m_lastActionCount = actions.count(); + } else { + QCOMPARE(actions.count(), m_lastActionCount); // should be stable, i.e. selecting a recent dir shouldn't duplicate it + } + QAction *copyAction = actions.at(actions.count() - actionNumber); + QSignalSpy spy(&generator, SIGNAL(error(int,QString))); + + // When + copyAction->trigger(); + + // Then + QTRY_COMPARE(spy.count(), expectedErrorCode ? 1 : 0); + if (expectedErrorCode) { + QCOMPARE(spy.at(0).at(0).toInt(), expectedErrorCode); + } else { + QTRY_VERIFY(QFile::exists(m_destDir + "/srcfile")); + } + } + +private: + + static QStringList extractActionNames(const QMenu &menu) + { + QStringList ret; + foreach (const QAction *action, menu.actions()) { + ret.append(action->objectName()); + } + return ret; + } + + QTemporaryDir m_tempDir; + QString m_srcDir; + QString m_srcFile; + QTemporaryDir m_tempDestDir; + QString m_destDir; + QTemporaryDir m_nonWritableTempDir; + QWidget m_parentWidget; + QStringList m_recentDirs; + int m_lastActionCount; +}; + +QTEST_MAIN(KFileCopyToMenuTest) + +#include "kfilecopytomenutest.moc" + diff --git a/autotests/kfileitemactionstest.cpp b/autotests/kfileitemactionstest.cpp new file mode 100644 index 0000000..c2d8343 --- /dev/null +++ b/autotests/kfileitemactionstest.cpp @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + Copyright (C) 2014 Frank Reininghaus + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kfileitemactionstest.h" + +#include +#include + +#include +#include +#include +#include + +/** + * In KDE 4.x, calling KFileItemActions::setParentWidget(QWidget *widget) would + * result in 'widget' not only being the parent of any dialogs created by, + * KFileItemActions, but also of the actions. Nevertheless, the destructor of + * KFileItemActions deleted all actions it created. This could lead to the deletion + * of dangling pointers, and thus, a crash, if 'widget' was destroyed before the + * KFileItemActions instance. + */ +void KFileItemActionsTest::testSetParentWidget() +{ + KFileItemActions fileItemActions; + + // Create a widget and make it the parent for any dialogs created by fileItemActions. + QWidget *widget = new QWidget(); + fileItemActions.setParentWidget(widget); + + // Initialize fileItemActions with a KFileItemList that contains only the home URL. + KFileItemList items; + const QUrl homeUrl = QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()); + const KFileItem item(homeUrl, QStringLiteral("inode/directory")); + items << item; + const KFileItemListProperties properties(items); + fileItemActions.setItemListProperties(properties); + + // Create the "Open With" actions and add them to a menu. + QMenu menu; + fileItemActions.addOpenWithActionsTo(&menu); + + // Delete the widget. In KDE 4.x, this would also delete the "Open With" actions + // because they were children of the widget. We would then get a crash in the + // destructor of fileItemActions because it tried to delete dangling pointers. + delete widget; +} + +QTEST_MAIN(KFileItemActionsTest) diff --git a/autotests/kfileitemactionstest.h b/autotests/kfileitemactionstest.h new file mode 100644 index 0000000..70dbc9c --- /dev/null +++ b/autotests/kfileitemactionstest.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE project + Copyright (C) 2013 Frank Reininghaus + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KFILEITEMACTIONSTEST_H +#define KFILEITEMACTIONSTEST_H + +#include + +class KFileItemActionsTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testSetParentWidget(); +}; + +#endif diff --git a/autotests/kfileitemtest.cpp b/autotests/kfileitemtest.cpp new file mode 100644 index 0000000..1ba7627 --- /dev/null +++ b/autotests/kfileitemtest.cpp @@ -0,0 +1,613 @@ +/* This file is part of the KDE project + Copyright (C) 2006 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kfileitemtest.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include "kiotesthelper.h" + +#include + +QTEST_MAIN(KFileItemTest) + +void KFileItemTest::initTestCase() +{ +} + +void KFileItemTest::testPermissionsString() +{ + // Directory + QTemporaryDir tempDir; + KFileItem dirItem(QUrl::fromLocalFile(tempDir.path() + '/')); + QCOMPARE((uint)dirItem.permissions(), (uint)0700); + QCOMPARE(dirItem.permissionsString(), QStringLiteral("drwx------")); + QVERIFY(dirItem.isReadable()); + + // File + QFile file(tempDir.path() + "/afile"); + QVERIFY(file.open(QIODevice::WriteOnly)); + file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadOther); // 0604 + KFileItem fileItem(QUrl::fromLocalFile(file.fileName()), QString(), KFileItem::Unknown); + QCOMPARE((uint)fileItem.permissions(), (uint)0604); + QCOMPARE(fileItem.permissionsString(), QStringLiteral("-rw----r--")); + QVERIFY(fileItem.isReadable()); + + // Symlink to file + QString symlink = tempDir.path() + "/asymlink"; + QVERIFY(file.link(symlink)); + QUrl symlinkUrl = QUrl::fromLocalFile(symlink); + KFileItem symlinkItem(symlinkUrl, QString(), KFileItem::Unknown); + QCOMPARE((uint)symlinkItem.permissions(), (uint)0604); + // This is a bit different from "ls -l": we get the 'l' but we see the permissions of the target. + // This is actually useful though; the user sees it's a link, and can check if he can read the [target] file. + QCOMPARE(symlinkItem.permissionsString(), QStringLiteral("lrw----r--")); + QVERIFY(symlinkItem.isReadable()); + + // Symlink to directory (#162544) + QVERIFY(QFile::remove(symlink)); + QVERIFY(QFile(tempDir.path() + '/').link(symlink)); + KFileItem symlinkToDirItem(symlinkUrl, QString(), KFileItem::Unknown); + QCOMPARE((uint)symlinkToDirItem.permissions(), (uint)0700); + QCOMPARE(symlinkToDirItem.permissionsString(), QStringLiteral("lrwx------")); +} + +void KFileItemTest::testNull() +{ + KFileItem null; + QVERIFY(null.isNull()); + KFileItem fileItem(QUrl::fromLocalFile(QStringLiteral("/")), QString(), KFileItem::Unknown); + QVERIFY(!fileItem.isNull()); + null = fileItem; // ok, now 'null' isn't so null anymore + QVERIFY(!null.isNull()); + QVERIFY(null.isReadable()); + QVERIFY(!null.isHidden()); +} + +void KFileItemTest::testDoesNotExist() +{ + KFileItem fileItem(QUrl::fromLocalFile(QStringLiteral("/doesnotexist")), QString(), KFileItem::Unknown); + QVERIFY(!fileItem.isNull()); + QVERIFY(!fileItem.isReadable()); + QVERIFY(fileItem.user().isEmpty()); + QVERIFY(fileItem.group().isEmpty()); +} + +void KFileItemTest::testDetach() +{ + KFileItem fileItem(QUrl::fromLocalFile(QStringLiteral("/one")), QString(), KFileItem::Unknown); + QCOMPARE(fileItem.name(), QStringLiteral("one")); + KFileItem fileItem2(fileItem); + QVERIFY(fileItem == fileItem2); + QVERIFY(fileItem.d == fileItem2.d); + fileItem2.setName(QStringLiteral("two")); + QCOMPARE(fileItem2.name(), QStringLiteral("two")); + QCOMPARE(fileItem.name(), QStringLiteral("one")); // it detached + QVERIFY(fileItem == fileItem2); + QVERIFY(fileItem.d != fileItem2.d); + + fileItem = fileItem2; + QCOMPARE(fileItem.name(), QStringLiteral("two")); + QVERIFY(fileItem == fileItem2); + QVERIFY(fileItem.d == fileItem2.d); + QVERIFY(!(fileItem != fileItem2)); +} + +void KFileItemTest::testBasic() +{ + QTemporaryFile file; + QVERIFY(file.open()); + QFile fileObj(file.fileName()); + QVERIFY(fileObj.open(QIODevice::WriteOnly)); + fileObj.write(QByteArray("Hello")); + fileObj.close(); + + QUrl url = QUrl::fromLocalFile(file.fileName()); + KFileItem fileItem(url, QString(), KFileItem::Unknown); + QCOMPARE(fileItem.text(), url.fileName()); + QVERIFY(fileItem.isLocalFile()); + QCOMPARE(fileItem.localPath(), url.path()); + QCOMPARE(fileItem.size(), KIO::filesize_t(5)); + QVERIFY(fileItem.linkDest().isEmpty()); + QVERIFY(!fileItem.isHidden()); + QVERIFY(fileItem.isReadable()); + QVERIFY(fileItem.isWritable()); + QVERIFY(fileItem.isFile()); + QVERIFY(!fileItem.isDir()); + QVERIFY(!fileItem.isDesktopFile()); + QCOMPARE(fileItem.user(), KUser().loginName()); +} + +void KFileItemTest::testRootDirectory() +{ + const QString rootPath = QDir::rootPath(); + QUrl url = QUrl::fromLocalFile(rootPath); + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + KFileItem fileItem(entry, url); + QCOMPARE(fileItem.text(), QStringLiteral(".")); + QVERIFY(fileItem.isLocalFile()); + QCOMPARE(fileItem.localPath(), url.path()); + QVERIFY(fileItem.linkDest().isEmpty()); + QVERIFY(!fileItem.isHidden()); + QVERIFY(!fileItem.isFile()); + QVERIFY(fileItem.isDir()); + QVERIFY(!fileItem.isDesktopFile()); +} + +void KFileItemTest::testHiddenFile() +{ + QTemporaryDir tempDir; + QFile file(tempDir.path() + "/.hiddenfile"); + QVERIFY(file.open(QIODevice::WriteOnly)); + KFileItem fileItem(QUrl::fromLocalFile(file.fileName()), QString(), KFileItem::Unknown); + QCOMPARE(fileItem.text(), QStringLiteral(".hiddenfile")); + QVERIFY(fileItem.isLocalFile()); + QVERIFY(fileItem.isHidden()); +} + +void KFileItemTest::testMimeTypeOnDemand() +{ + QTemporaryFile file; + QVERIFY(file.open()); + + { + KFileItem fileItem(QUrl::fromLocalFile(file.fileName())); + fileItem.setDelayedMimeTypes(true); + QVERIFY(fileItem.currentMimeType().isDefault()); + QVERIFY(!fileItem.isMimeTypeKnown()); + QVERIFY(!fileItem.isFinalIconKnown()); + //qDebug() << fileItem.determineMimeType().name(); + QCOMPARE(fileItem.determineMimeType().name(), QStringLiteral("application/x-zerosize")); + QCOMPARE(fileItem.mimetype(), QStringLiteral("application/x-zerosize")); + QVERIFY(fileItem.isMimeTypeKnown()); + QVERIFY(fileItem.isFinalIconKnown()); + } + + { + // Calling mimeType directly also does mimetype determination + KFileItem fileItem(QUrl::fromLocalFile(file.fileName())); + fileItem.setDelayedMimeTypes(true); + QVERIFY(!fileItem.isMimeTypeKnown()); + QCOMPARE(fileItem.mimetype(), QStringLiteral("application/x-zerosize")); + QVERIFY(fileItem.isMimeTypeKnown()); + } + + { + // Calling overlays should NOT do mimetype determination (#237668) + KFileItem fileItem(QUrl::fromLocalFile(file.fileName())); + fileItem.setDelayedMimeTypes(true); + QVERIFY(!fileItem.isMimeTypeKnown()); + fileItem.overlays(); + QVERIFY(!fileItem.isMimeTypeKnown()); + } + + { + QTemporaryFile file; + QVERIFY(file.open()); + // Check whether mime-magic is used. + // No known extension, so it should be used by determineMimeType. + file.write(QByteArray("%PDF-")); + QString fileName = file.fileName(); + QVERIFY(!fileName.isEmpty()); + file.close(); + KFileItem fileItem(QUrl::fromLocalFile(fileName)); + fileItem.setDelayedMimeTypes(true); + QCOMPARE(fileItem.currentMimeType().name(), QLatin1String("application/octet-stream")); + QVERIFY(fileItem.currentMimeType().isValid()); + QVERIFY(fileItem.currentMimeType().isDefault()); + QVERIFY(!fileItem.isMimeTypeKnown()); + QCOMPARE(fileItem.determineMimeType().name(), QStringLiteral("application/pdf")); + QCOMPARE(fileItem.mimetype(), QStringLiteral("application/pdf")); + } + + { + QTemporaryFile file(QDir::tempPath() + QLatin1String("/kfileitemtest_XXXXXX.txt")); + QVERIFY(file.open()); + // Check whether mime-magic is used. + // Known extension, so it should NOT be used. + file.write(QByteArray("("filename"); + QTest::addColumn("expectedText"); + + QTest::newRow("simple") << "filename" << "filename"; + QTest::newRow("/ at end") << QString(QStringLiteral("foo") + QChar(0x2044)) << QString(QStringLiteral("foo") + QChar(0x2044)); + QTest::newRow("/ at begin") << QString(QChar(0x2044)) << QString(QChar(0x2044)); +} + +void KFileItemTest::testDecodeFileName() +{ + QFETCH(QString, filename); + QFETCH(QString, expectedText); + QCOMPARE(KIO::decodeFileName(filename), expectedText); +} + +void KFileItemTest::testEncodeFileName_data() +{ + QTest::addColumn("text"); + QTest::addColumn("expectedFileName"); + + QTest::newRow("simple") << "filename" << "filename"; + QTest::newRow("/ at end") << "foo/" << QString(QStringLiteral("foo") + QChar(0x2044)); + QTest::newRow("/ at begin") << "/" << QString(QChar(0x2044)); +} + +void KFileItemTest::testEncodeFileName() +{ + QFETCH(QString, text); + QFETCH(QString, expectedFileName); + QCOMPARE(KIO::encodeFileName(text), expectedFileName); +} + +void KFileItemTest::testListProperties_data() +{ + QTest::addColumn("itemDescriptions"); + QTest::addColumn("expectedReading"); + QTest::addColumn("expectedDeleting"); + QTest::addColumn("expectedIsLocal"); + QTest::addColumn("expectedIsDirectory"); + QTest::addColumn("expectedMimeType"); + QTest::addColumn("expectedMimeGroup"); + + QTest::newRow("one file") << "f" << true << true << true << false << "text/plain" << "text"; + QTest::newRow("one dir") << "d" << true << true << true << true << "inode/directory" << "inode"; + QTest::newRow("root dir") << "/" << true << false << true << true << "inode/directory" << "inode"; + QTest::newRow("file+dir") << "fd" << true << true << true << false << "" << ""; + QTest::newRow("two dirs") << "dd" << true << true << true << true << "inode/directory" << "inode"; + QTest::newRow("dir+root dir") << "d/" << true << false << true << true << "inode/directory" << "inode"; + QTest::newRow("two (text+html) files") << "ff" << true << true << true << false << "" << "text"; + QTest::newRow("three (text+html+empty) files") << "fff" << true << true << true << false << "" << ""; + QTest::newRow("http url") << "h" << true << true /*says kio_http...*/ + << false << false << "application/octet-stream" << "application"; + QTest::newRow("2 http urls") << "hh" << true << true /*says kio_http...*/ + << false << false << "application/octet-stream" << "application"; +} + +void KFileItemTest::testListProperties() +{ + QFETCH(QString, itemDescriptions); + QFETCH(bool, expectedReading); + QFETCH(bool, expectedDeleting); + QFETCH(bool, expectedIsLocal); + QFETCH(bool, expectedIsDirectory); + QFETCH(QString, expectedMimeType); + QFETCH(QString, expectedMimeGroup); + + QTemporaryDir tempDir; + QDir baseDir(tempDir.path()); + KFileItemList items; + for (int i = 0; i < itemDescriptions.size(); ++i) { + QString fileName = tempDir.path() + "/file" + QString::number(i); + switch (itemDescriptions[i].toLatin1()) { + case 'f': { + if (i == 1) { // 2nd file is html + fileName += QLatin1String(".html"); + } + QFile file(fileName); + QVERIFY(file.open(QIODevice::WriteOnly)); + if (i != 2) { // 3rd file is empty + file.write("Hello"); + } + items << KFileItem(QUrl::fromLocalFile(fileName), QString(), KFileItem::Unknown); + } + break; + case 'd': + QVERIFY(baseDir.mkdir(fileName)); + items << KFileItem(QUrl::fromLocalFile(fileName), QString(), KFileItem::Unknown); + break; + case '/': + items << KFileItem(QUrl::fromLocalFile(QStringLiteral("/")), QString(), KFileItem::Unknown); + break; + case 'h': + items << KFileItem(QUrl(QStringLiteral("http://www.kde.org")), QString(), KFileItem::Unknown); + break; + default: + QVERIFY(false); + } + } + KFileItemListProperties props(items); + QCOMPARE(props.supportsReading(), expectedReading); + QCOMPARE(props.supportsDeleting(), expectedDeleting); + QCOMPARE(props.isLocal(), expectedIsLocal); + QCOMPARE(props.isDirectory(), expectedIsDirectory); + QCOMPARE(props.mimeType(), expectedMimeType); + QCOMPARE(props.mimeGroup(), expectedMimeGroup); +} + +void KFileItemTest::testIconNameForUrl_data() +{ + QTest::addColumn("url"); + QTest::addColumn("expectedIcon"); + + QTest::newRow("root") << "file:/" << "folder"; // the icon comes from KProtocolInfo + if (QFile::exists(QStringLiteral("/tmp"))) { + QTest::newRow("subdir") << "file:/tmp" << "inode-directory"; + } + // TODO more tests +} + +void KFileItemTest::testIconNameForUrl() +{ + QFETCH(QString, url); + QFETCH(QString, expectedIcon); + + QCOMPARE(KIO::iconNameForUrl(QUrl(url)), expectedIcon); +} + +void KFileItemTest::testMimetypeForRemoteFolder() +{ + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("foo")); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + QUrl url(QStringLiteral("smb://remoteFolder/foo")); + KFileItem fileItem(entry, url); + + QCOMPARE(fileItem.mimetype(), QStringLiteral("inode/directory")); +} + +void KFileItemTest::testMimetypeForRemoteFolderWithFileType() +{ + QString udsMimeType = QStringLiteral("application/x-smb-workgroup"); + QVERIFY2(QMimeDatabase().mimeTypeForName(udsMimeType).isValid(), + qPrintable(QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).join(':'))); // kcoreaddons installed? XDG_DATA_DIRS set? + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("foo")); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, udsMimeType); + + QUrl url(QStringLiteral("smb://remoteFolder/foo")); + KFileItem fileItem(entry, url); + + QCOMPARE(fileItem.mimetype(), udsMimeType); +} + +void KFileItemTest::testCurrentMimetypeForRemoteFolder() +{ + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("foo")); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + QUrl url(QStringLiteral("smb://remoteFolder/foo")); + KFileItem fileItem(entry, url); + + QCOMPARE(fileItem.currentMimeType().name(), QStringLiteral("inode/directory")); +} + +void KFileItemTest::testCurrentMimetypeForRemoteFolderWithFileType() +{ + QString udsMimeType = QStringLiteral("application/x-smb-workgroup"); + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("foo")); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, udsMimeType); + + QUrl url(QStringLiteral("smb://remoteFolder/foo")); + KFileItem fileItem(entry, url); + + QCOMPARE(fileItem.currentMimeType().name(), udsMimeType); +} + +void KFileItemTest::testIconNameForCustomFolderIcons() +{ + // Custom folder icons should be displayed (bug 350612) + + const QString iconName = QStringLiteral("folder-music"); + + QTemporaryDir tempDir; + const QUrl url = QUrl::fromLocalFile(tempDir.path()); + KDesktopFile cfg(tempDir.path() + QLatin1String("/.directory")); + cfg.desktopGroup().writeEntry("Icon", iconName); + cfg.sync(); + + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + KFileItem fileItem(entry, url); + + QCOMPARE(fileItem.iconName(), iconName); +} + +void KFileItemTest::testIconNameForStandardPath() +{ + const QString iconName = QStringLiteral("folder-videos"); + const QUrl url = QUrl::fromLocalFile(QDir::homePath() + QLatin1String("/Videos")); + QStandardPaths::setTestModeEnabled(true); + + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + KFileItem fileItem(entry, url); + + QCOMPARE(fileItem.iconName(), iconName); +} + +#ifndef Q_OS_WIN // user/group/other write permissions are not handled on windows + +void KFileItemTest::testIsReadable_data() +{ + QTest::addColumn("mode"); + QTest::addColumn("readable"); + + QTest::newRow("fully-readable") << 0444 << true; + QTest::newRow("user-readable") << 0400 << true; + QTest::newRow("not-readable-by-us") << 0004 << false; + QTest::newRow("not-readable-at-all") << 0000 << false; +} + +void KFileItemTest::testIsReadable() +{ + QFETCH(int, mode); + QFETCH(bool, readable); + + QTemporaryFile file; + QVERIFY(file.open()); + int ret = fchmod(file.handle(), (mode_t)mode); + QCOMPARE(ret, 0); + + KFileItem fileItem(QUrl::fromLocalFile(file.fileName())); + QCOMPARE(fileItem.isReadable(), readable); +} + +#endif diff --git a/autotests/kfileitemtest.h b/autotests/kfileitemtest.h new file mode 100644 index 0000000..b7effc0 --- /dev/null +++ b/autotests/kfileitemtest.h @@ -0,0 +1,67 @@ +/* This file is part of the KDE project + Copyright (C) 2006 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef KFILEITEMTEST_H +#define KFILEITEMTEST_H + +#include + +class KFileItemTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testPermissionsString(); + void testNull(); + void testDoesNotExist(); + void testDetach(); + void testBasic(); + void testRootDirectory(); + void testHiddenFile(); + void testMimeTypeOnDemand(); + void testCmp(); + void testRename(); + void testRefresh(); + void testDotDirectory(); + void testMimetypeForRemoteFolder(); + void testMimetypeForRemoteFolderWithFileType(); + void testCurrentMimetypeForRemoteFolder(); + void testCurrentMimetypeForRemoteFolderWithFileType(); + void testIconNameForCustomFolderIcons(); + void testIconNameForStandardPath(); + +#ifndef Q_OS_WIN + void testIsReadable_data(); + void testIsReadable(); +#endif + + void testDecodeFileName_data(); + void testDecodeFileName(); + void testEncodeFileName_data(); + void testEncodeFileName(); + + // KFileItemListProperties tests + void testListProperties_data(); + void testListProperties(); + + // KIO global tests + void testIconNameForUrl_data(); + void testIconNameForUrl(); +}; + +#endif diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp new file mode 100644 index 0000000..dcdbab9 --- /dev/null +++ b/autotests/kfileplacesmodeltest.cpp @@ -0,0 +1,698 @@ +/* This file is part of the KDE project + Copyright (C) 2007 Kevin Ottens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + +*/ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#ifdef Q_OS_WIN +//c:\ as root for windows +#define KDE_ROOT_PATH "C:\\" +#else +#define KDE_ROOT_PATH "/" +#endif + +// Avoid QHash randomization so that the order of the devices is stable +static void seedInit() +{ + qputenv("QT_HASH_SEED", "0"); + qputenv("QT_NO_CPU_FEATURE", "sse4.2"); +} +Q_CONSTRUCTOR_FUNCTION(seedInit) + +class KFilePlacesModelTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void testInitialState(); + void testInitialList(); + void testReparse(); + void testInternalBookmarksHaveIds(); + void testHiding(); + void testMove(); + void testPlacesLifecycle(); + void testDevicePlugging(); + void testDragAndDrop(); + void testDeviceSetupTeardown(); + +private: + QStringList placesUrls() const; + QDBusInterface *fakeManager(); + QDBusInterface *fakeDevice(const QString &udi); + + KFilePlacesModel *m_places; + KFilePlacesModel *m_places2; // To check that they always stay in sync + // actually supposed to work across processes, + // but much harder to test + + QMap m_interfacesMap; +}; + +static QString bookmarksFile() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; +} + +void KFilePlacesModelTest::initTestCase() +{ + qputenv("KDE_FORK_SLAVES", "yes"); // to avoid a runtime dependency on klauncher + QStandardPaths::setTestModeEnabled(true); + + // Ensure we'll have a clean bookmark file to start + QFile::remove(bookmarksFile()); + + qRegisterMetaType(); + const QString fakeHw = QFINDTESTDATA("fakecomputer.xml"); + QVERIFY(!fakeHw.isEmpty()); + qputenv("SOLID_FAKEHW", QFile::encodeName(fakeHw)); + m_places = new KFilePlacesModel(); + m_places2 = new KFilePlacesModel(); +} + +void KFilePlacesModelTest::cleanupTestCase() +{ + delete m_places; + delete m_places2; + qDeleteAll(m_interfacesMap); + QFile::remove(bookmarksFile()); +} + +QStringList KFilePlacesModelTest::placesUrls() const +{ + QStringList urls; + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + urls << m_places->url(index).toDisplayString(QUrl::PreferLocalFile); + } + return urls; +} + +#define CHECK_PLACES_URLS(urls) \ + if (placesUrls() != urls) { \ + qDebug() << "Expected:" << urls; \ + qDebug() << "Got:" << placesUrls(); \ + QCOMPARE(placesUrls(), urls); \ + } \ + for (int row = 0; row < urls.size(); ++row) { \ + QModelIndex index = m_places->index(row, 0); \ + \ + QCOMPARE(m_places->url(index).toString(), QUrl::fromUserInput(urls[row]).toString()); \ + QCOMPARE(m_places->data(index, KFilePlacesModel::UrlRole).toUrl(), \ + QUrl(m_places->url(index))); \ + \ + index = m_places2->index(row, 0); \ + \ + QCOMPARE(m_places2->url(index).toString(), QUrl::fromUserInput(urls[row]).toString()); \ + QCOMPARE(m_places2->data(index, KFilePlacesModel::UrlRole).toUrl(), \ + QUrl(m_places2->url(index))); \ + } \ + \ + QCOMPARE(urls.size(), m_places->rowCount()); \ + QCOMPARE(urls.size(), m_places2->rowCount()); + +QDBusInterface *KFilePlacesModelTest::fakeManager() +{ + return fakeDevice(QStringLiteral("/org/kde/solid/fakehw")); +} + +QDBusInterface *KFilePlacesModelTest::fakeDevice(const QString &udi) +{ + if (m_interfacesMap.contains(udi)) { + return m_interfacesMap[udi]; + } + + QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi); + m_interfacesMap[udi] = iface; + + return iface; +} + +void KFilePlacesModelTest::testInitialState() +{ + QCOMPARE(m_places->rowCount(), 4); // when the xbel file is empty, KFilePlacesModel fills it with 4 default items + QCoreApplication::processEvents(); // Devices have a delayed loading + QCOMPARE(m_places->rowCount(), 9); +} + +static const QStringList initialListOfUrls() +{ + return QStringList() << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); +} + +void KFilePlacesModelTest::testInitialList() +{ + const QStringList urls = initialListOfUrls(); + CHECK_PLACES_URLS(urls); +} + +void KFilePlacesModelTest::testReparse() +{ + QStringList urls; + + // add item + + m_places->addPlace(QStringLiteral("foo"), QUrl::fromLocalFile(QStringLiteral("/foo")), + QString(), QString()); + + urls = initialListOfUrls(); + urls << QStringLiteral("/foo"); + CHECK_PLACES_URLS(urls); + + // reparse the bookmark file + + KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); + + bookmarkManager->notifyCompleteChange(QString()); + + // check if they are the same + + CHECK_PLACES_URLS(urls); + + // try to remove item + + m_places->removePlace(m_places->index(9, 0)); + + urls = initialListOfUrls(); + CHECK_PLACES_URLS(urls); +} + +void KFilePlacesModelTest::testInternalBookmarksHaveIds() +{ + KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); + KBookmarkGroup root = bookmarkManager->root(); + + // Verify every entry has an id or an udi + KBookmark bookmark = root.first(); + while (!bookmark.isNull()) { + QVERIFY(!bookmark.metaDataItem(QStringLiteral("ID")).isEmpty() || !bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()); + // It's mutualy exclusive though + QVERIFY(bookmark.metaDataItem(QStringLiteral("ID")).isEmpty() || bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()); + + bookmark = root.next(bookmark); + } + + // Verify that adding a bookmark behind its back the model gives it an id + // (in real life it requires the user to modify the file by hand, + // unlikely but better safe than sorry). + // It induces a small race condition which means several ids will be + // successively set on the same bookmark but no big deal since it won't + // break the system + KBookmark foo = root.addBookmark(QStringLiteral("Foo"), QUrl(QStringLiteral("file:/foo")), QStringLiteral("red-folder")); + QCOMPARE(foo.text(), QStringLiteral("Foo")); + QVERIFY(foo.metaDataItem(QStringLiteral("ID")).isEmpty()); + bookmarkManager->emitChanged(root); + QCOMPARE(foo.text(), QStringLiteral("Foo")); + QVERIFY(!foo.metaDataItem(QStringLiteral("ID")).isEmpty()); + + // Verify that all the ids are different + bookmark = root.first(); + QSet ids; + while (!bookmark.isNull()) { + QString id; + if (!bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()) { + id = bookmark.metaDataItem(QStringLiteral("UDI")); + } else { + id = bookmark.metaDataItem(QStringLiteral("ID")); + } + + QVERIFY2(!ids.contains(id), "Duplicated ID found!"); + ids << id; + bookmark = root.next(bookmark); + } + + // Cleanup foo + root.deleteBookmark(foo); + bookmarkManager->emitChanged(root); +} + +void KFilePlacesModelTest::testHiding() +{ + // Verify that nothing is hidden + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + QVERIFY(!m_places->isHidden(index)); + } + + QModelIndex a = m_places->index(2, 0); + QModelIndex b = m_places->index(6, 0); + + QList args; + QSignalSpy spy(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + // Verify that hidden is taken into account and is not global + m_places->setPlaceHidden(a, true); + QVERIFY(m_places->isHidden(a)); + QVERIFY(m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); + QVERIFY(!m_places->isHidden(b)); + QVERIFY(!m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); + QCOMPARE(spy.count(), 1); + args = spy.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), a); + QCOMPARE(args.at(1).toModelIndex(), a); + + m_places->setPlaceHidden(b, true); + QVERIFY(m_places->isHidden(a)); + QVERIFY(m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); + QVERIFY(m_places->isHidden(b)); + QVERIFY(m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); + QCOMPARE(spy.count(), 1); + args = spy.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), b); + QCOMPARE(args.at(1).toModelIndex(), b); + + m_places->setPlaceHidden(a, false); + m_places->setPlaceHidden(b, false); + QVERIFY(!m_places->isHidden(a)); + QVERIFY(!m_places->data(a, KFilePlacesModel::HiddenRole).toBool()); + QVERIFY(!m_places->isHidden(b)); + QVERIFY(!m_places->data(b, KFilePlacesModel::HiddenRole).toBool()); + QCOMPARE(spy.count(), 2); + args = spy.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), a); + QCOMPARE(args.at(1).toModelIndex(), a); + args = spy.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), b); + QCOMPARE(args.at(1).toModelIndex(), b); +} + +void KFilePlacesModelTest::testMove() +{ + QList args; + QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); + QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); + KBookmarkGroup root = bookmarkManager->root(); + KBookmark trash = m_places->bookmarkForIndex(m_places->index(3, 0)); + KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(2, 0)); + + // Move the trash at the end of the list + KBookmark last = root.first(); + while (!root.next(last).isNull()) { + last = root.next(last); + } + root.moveBookmark(trash, last); + bookmarkManager->emitChanged(root); + + QStringList urls; + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign") << QStringLiteral("trash:/"); + + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 8); + QCOMPARE(args.at(2).toInt(), 8); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); + + // Move the trash at the beginning of the list + root.moveBookmark(trash, KBookmark()); + bookmarkManager->emitChanged(root); + + urls.clear(); + urls << QStringLiteral("trash:/") << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 0); + QCOMPARE(args.at(2).toInt(), 0); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 9); + QCOMPARE(args.at(2).toInt(), 9); + + // Move the trash in the list (at its original place) + root.moveBookmark(trash, before_trash); + bookmarkManager->emitChanged(root); + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 0); + QCOMPARE(args.at(2).toInt(), 0); +} + +void KFilePlacesModelTest::testDragAndDrop() +{ + QList args; + QSignalSpy spy_moved(m_places, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int))); + + // Monitor rowsInserted() and rowsRemoved() to ensure they are never emitted: + // Moving with drag and drop is expected to emit rowsMoved() + QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); + QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + // Move the trash at the end of the list + QModelIndexList indexes; + indexes << m_places->index(3, 0); + QMimeData *mimeData = m_places->mimeData(indexes); + QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, -1, 0, QModelIndex())); + + QStringList urls; + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign") << QStringLiteral("trash:/"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 0); + QCOMPARE(spy_moved.count(), 1); + args = spy_moved.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(4).toInt(), 9); + + // Move the trash at the beginning of the list + indexes.clear(); + indexes << m_places->index(8, 0); + mimeData = m_places->mimeData(indexes); + QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 0, 0, QModelIndex())); + + urls.clear(); + urls << QStringLiteral("trash:/") << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 0); + QCOMPARE(spy_moved.count(), 1); + args = spy_moved.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 8); + QCOMPARE(args.at(2).toInt(), 8); + QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(4).toInt(), 0); + + // Move the trash in the list (at its original place) + indexes.clear(); + indexes << m_places->index(0, 0); + mimeData = m_places->mimeData(indexes); + QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 4, 0, QModelIndex())); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 0); + QCOMPARE(spy_moved.count(), 1); + args = spy_moved.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 0); + QCOMPARE(args.at(2).toInt(), 0); + QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(4).toInt(), 4); + + // Dropping on an item is not allowed + indexes.clear(); + indexes << m_places->index(4, 0); + mimeData = m_places->mimeData(indexes); + QVERIFY(!m_places->dropMimeData(mimeData, Qt::MoveAction, -1, 0, m_places->index(2, 0))); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 0); + QCOMPARE(spy_moved.count(), 0); +} + +void KFilePlacesModelTest::testPlacesLifecycle() +{ + QList args; + QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); + QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); + QSignalSpy spy_changed(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo"))); + + QStringList urls; + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign") << QStringLiteral("/home/foo"); + + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 9); + QCOMPARE(args.at(2).toInt(), 9); + QCOMPARE(spy_removed.count(), 0); + + KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); + KBookmarkGroup root = bookmarkManager->root(); + KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(2, 0)); + KBookmark foo = m_places->bookmarkForIndex(m_places->index(9, 0)); + + root.moveBookmark(foo, before_trash); + bookmarkManager->emitChanged(root); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/home/foo") + << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 10); + QCOMPARE(args.at(2).toInt(), 10); + + m_places->editPlace(m_places->index(3, 0), QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/mnt/foo"))); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/mnt/foo") + << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 0); + QCOMPARE(spy_changed.count(), 1); + args = spy_changed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), m_places->index(3, 0)); + QCOMPARE(args.at(1).toModelIndex(), m_places->index(3, 0)); + + foo = m_places->bookmarkForIndex(m_places->index(3, 0)); + foo.setFullText(QStringLiteral("Bar")); + bookmarkManager->notifyCompleteChange(QString()); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/mnt/foo") + << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 0); + QCOMPARE(spy_changed.count(), 10); + args = spy_changed[3]; + QCOMPARE(args.at(0).toModelIndex(), m_places->index(3, 0)); + QCOMPARE(args.at(1).toModelIndex(), m_places->index(3, 0)); + spy_changed.clear(); + + m_places->removePlace(m_places->index(3, 0)); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); + + m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo")), QString(), QString(), m_places->index(1, 0)); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral("/home/foo") << QStringLiteral(KDE_ROOT_PATH) + << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 2); + QCOMPARE(args.at(2).toInt(), 2); + QCOMPARE(spy_removed.count(), 0); + + m_places->removePlace(m_places->index(2, 0)); +} + +void KFilePlacesModelTest::testDevicePlugging() +{ + QList args; + QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); + QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); + + QStringList urls; + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 6); + QCOMPARE(args.at(2).toInt(), 6); + + fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 6); + QCOMPARE(args.at(2).toInt(), 6); + QCOMPARE(spy_removed.count(), 0); + + // Move the device in the list, and check that it memorizes the position across plug/unplug + + KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); + KBookmarkGroup root = bookmarkManager->root(); + KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(2, 0)); + KBookmark device = root.first(); // The device we'll move is the 7th bookmark + for (int i = 0; i < 6; i++) { + device = root.next(device); + } + + root.moveBookmark(device, before_trash); + bookmarkManager->emitChanged(root); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/media/XO-Y4") + << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 7); + QCOMPARE(args.at(2).toInt(), 7); + + fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) + << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 0); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); + + fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/media/XO-Y4") + << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(spy_removed.count(), 0); + + KBookmark seventh = root.first(); + for (int i = 0; i < 6; i++) { + seventh = root.next(seventh); + } + root.moveBookmark(device, seventh); + bookmarkManager->emitChanged(root); + + urls.clear(); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + CHECK_PLACES_URLS(urls); + QCOMPARE(spy_inserted.count(), 1); + args = spy_inserted.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 6); + QCOMPARE(args.at(2).toInt(), 6); + QCOMPARE(spy_removed.count(), 1); + args = spy_removed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); +} + +void KFilePlacesModelTest::testDeviceSetupTeardown() +{ + QList args; + QSignalSpy spy_changed(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + fakeDevice(QStringLiteral("/org/kde/solid/fakehw/volume_part1_size_993284096/StorageAccess"))->call(QStringLiteral("teardown")); + + QCOMPARE(spy_changed.count(), 1); + args = spy_changed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex().row(), 6); + QCOMPARE(args.at(1).toModelIndex().row(), 6); + + fakeDevice(QStringLiteral("/org/kde/solid/fakehw/volume_part1_size_993284096/StorageAccess"))->call(QStringLiteral("setup")); + + QCOMPARE(spy_changed.count(), 1); + args = spy_changed.takeFirst(); + QCOMPARE(args.at(0).toModelIndex().row(), 6); + QCOMPARE(args.at(1).toModelIndex().row(), 6); +} + +QTEST_MAIN(KFilePlacesModelTest) + +#include "kfileplacesmodeltest.moc" diff --git a/autotests/kfilewidgettest.cpp b/autotests/kfilewidgettest.cpp new file mode 100644 index 0000000..de48a20 --- /dev/null +++ b/autotests/kfilewidgettest.cpp @@ -0,0 +1,129 @@ +/* This file is part of the KIO framework tests + + Copyright (c) 2016 Albert Astals Cid + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include "kfilewidget.h" + +#include + +#include +#include +#include + +/** + * Unit test for KFileWidget + */ +class KFileWidgetTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase() + { + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + + QStandardPaths::setTestModeEnabled(true); + + QVERIFY(QDir::homePath() != QDir::tempPath()); + } + + void cleanupTestCase() + { + } + + QWidget *findLocationLabel(QWidget *parent) + { + const QList labels = parent->findChildren(); + foreach(QLabel *label, labels) { + if (label->text() == i18n("&Name:")) + return label->buddy(); + } + Q_ASSERT(false); + return 0; + } + + void testFocusOnLocationEdit() + { + KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); + fw.show(); + QTest::qWaitForWindowActive(&fw); + + QVERIFY(findLocationLabel(&fw)->hasFocus()); + } + + void testFocusOnLocationEditChangeDir() + { + KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); + fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); + fw.show(); + QTest::qWaitForWindowActive(&fw); + + QVERIFY(findLocationLabel(&fw)->hasFocus()); + } + + void testFocusOnLocationEditChangeDir2() + { + KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); + fw.show(); + QTest::qWaitForWindowActive(&fw); + + fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); + + QVERIFY(findLocationLabel(&fw)->hasFocus()); + } + + void testFocusOnDirOps() + { + KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); + fw.show(); + QTest::qWaitForWindowActive(&fw); + + const QList nav = fw.findChildren(); + QCOMPARE(nav.count(), 1); + nav[0]->setFocus(); + + fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); + + const QList ops = fw.findChildren(); + QCOMPARE(ops.count(), 1); + QVERIFY(ops[0]->hasFocus()); + } + + void testGetStartUrl() + { + QString recentDirClass; + QString outFileName; + QUrl localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachmentDir")), recentDirClass, outFileName); + QCOMPARE(recentDirClass, QStringLiteral(":attachmentDir")); + QCOMPARE(localUrl.path(), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); + QVERIFY(outFileName.isEmpty()); + + localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachments/foo.txt?global")), recentDirClass, outFileName); + QCOMPARE(recentDirClass, QStringLiteral("::attachments")); + QCOMPARE(localUrl.path(), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); + QCOMPARE(outFileName, QStringLiteral("foo.txt")); + } +}; + +QTEST_MAIN(KFileWidgetTest) + +#include "kfilewidgettest.moc" diff --git a/autotests/kiotesthelper.h b/autotests/kiotesthelper.h new file mode 100644 index 0000000..aa79b15 --- /dev/null +++ b/autotests/kiotesthelper.h @@ -0,0 +1,205 @@ +/* This file is part of the KDE project + Copyright (C) 2006 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +// This file can only be included once in a given binary + +#include +#include +#include +#include +#include +#ifdef Q_OS_UNIX +#include +#else +#include +#endif +#include + +#include "kioglobal_p.h" + +QString homeTmpDir() +{ + const QString dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/kiotests/"); + if (!QFile::exists(dir)) { + const bool ok = QDir().mkpath(dir); + if (!ok) { + qFatal("Couldn't create %s", qPrintable(dir)); + } + } + return dir; +} + +static QDateTime s_referenceTimeStamp; + +static void setTimeStamp(const QString &path, const QDateTime &mtime) +{ +#ifdef Q_OS_UNIX + // Put timestamp in the past so that we can check that the listing is correct + struct utimbuf utbuf; + utbuf.actime = mtime.toTime_t(); + utbuf.modtime = utbuf.actime; + utime(QFile::encodeName(path), &utbuf); + //qDebug( "Time changed for %s", qPrintable( path ) ); +#elif defined(Q_OS_WIN) + struct _utimbuf utbuf; + utbuf.actime = mtime.toTime_t(); + utbuf.modtime = utbuf.actime; + _wutime(reinterpret_cast(path.utf16()), &utbuf); +#endif +} + +static void createTestFile(const QString &path, bool plainText = false) +{ + QDir().mkpath(QFileInfo(path).absolutePath()); + QFile f(path); + if (!f.open(QIODevice::WriteOnly)) { + qFatal("Couldn't create %s", qPrintable(path)); + } + QByteArray data(plainText ? "Hello world" : "Hello\0world", 11); + QCOMPARE(data.size(), 11); + f.write(data); + f.close(); + setTimeStamp(path, s_referenceTimeStamp); +} + +static void createTestSymlink(const QString &path, const QByteArray &target = "/IDontExist") +{ + QFile::remove(path); + bool ok = KIOPrivate::createSymlink(target, path); // broken symlink + if (!ok) { + qFatal("couldn't create symlink: %s", strerror(errno)); + } + QT_STATBUF buf; + QVERIFY(QT_LSTAT(QFile::encodeName(path), &buf) == 0); + QVERIFY((buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK); + //qDebug( "symlink %s created", qPrintable( path ) ); + QVERIFY(QFileInfo(path).isSymLink()); +} + +enum CreateTestDirectoryOptions { DefaultOptions = 0, NoSymlink = 1 }; +static inline void createTestDirectory(const QString &path, CreateTestDirectoryOptions opt = DefaultOptions) +{ + QDir dir; + bool ok = dir.mkdir(path); + if (!ok && !dir.exists()) { + qFatal("Couldn't create %s", qPrintable(path)); + } + createTestFile(path + "/testfile"); + if ((opt & NoSymlink) == 0) { +#ifndef Q_OS_WIN + createTestSymlink(path + "/testlink"); + QVERIFY(QFileInfo(path + "/testlink").isSymLink()); +#else + // to not change the filecount everywhere in the tests + createTestFile(path + "/testlink"); +#endif + } + setTimeStamp(path, s_referenceTimeStamp); +} + +#include +class PredefinedAnswerJobUiDelegate : public KIO::JobUiDelegateExtension +{ +public: + PredefinedAnswerJobUiDelegate() + : JobUiDelegateExtension(), + m_askFileRenameCalled(0), + m_askSkipCalled(0), + m_askDeleteCalled(0), + m_messageBoxCalled(0), + m_renameResult(KIO::R_SKIP), + m_skipResult(KIO::S_SKIP), + m_deleteResult(false), + m_messageBoxResult(0) + { + } + + KIO::RenameDialog_Result askFileRename(KJob *job, + const QString &caption, + const QUrl &src, + const QUrl &dest, + KIO::RenameDialog_Options options, + QString &newDest, + KIO::filesize_t = (KIO::filesize_t) - 1, + KIO::filesize_t = (KIO::filesize_t) - 1, + const QDateTime & = QDateTime(), + const QDateTime & = QDateTime(), + const QDateTime & = QDateTime(), + const QDateTime & = QDateTime()) Q_DECL_OVERRIDE { + Q_UNUSED(job) + Q_UNUSED(caption) + Q_UNUSED(src) + Q_UNUSED(dest) + Q_UNUSED(options) + Q_UNUSED(newDest) + ++m_askFileRenameCalled; + return m_renameResult; + } + + KIO::SkipDialog_Result askSkip(KJob *job, + KIO::SkipDialog_Options options, + const QString &error_text) Q_DECL_OVERRIDE { + Q_UNUSED(job) + Q_UNUSED(options) + Q_UNUSED(error_text) + ++m_askSkipCalled; + return m_skipResult; + } + + bool askDeleteConfirmation(const QList &urls, DeletionType deletionType, + ConfirmationType confirmationType) Q_DECL_OVERRIDE { + Q_UNUSED(urls); + Q_UNUSED(deletionType); + Q_UNUSED(confirmationType); + ++m_askDeleteCalled; + return m_deleteResult; + } + + int requestMessageBox(MessageBoxType type, const QString &text, + const QString &caption, + const QString &buttonYes, + const QString &buttonNo, + const QString &iconYes = QString(), + const QString &iconNo = QString(), + const QString &dontAskAgainName = QString(), + const KIO::MetaData &sslMetaData = KIO::MetaData()) Q_DECL_OVERRIDE { + Q_UNUSED(type); + Q_UNUSED(text); + Q_UNUSED(caption); + Q_UNUSED(buttonYes); + Q_UNUSED(buttonNo); + Q_UNUSED(iconYes); + Q_UNUSED(iconNo); + Q_UNUSED(dontAskAgainName); + Q_UNUSED(sslMetaData); + ++m_messageBoxCalled; + return m_messageBoxResult; + } + + // yeah, public, for get and reset. + int m_askFileRenameCalled; + int m_askSkipCalled; + int m_askDeleteCalled; + int m_messageBoxCalled; + + KIO::RenameDialog_Result m_renameResult; + KIO::SkipDialog_Result m_skipResult; + bool m_deleteResult; + int m_messageBoxResult; +}; diff --git a/autotests/klocalsocketservertest.cpp b/autotests/klocalsocketservertest.cpp new file mode 100644 index 0000000..81e56d4 --- /dev/null +++ b/autotests/klocalsocketservertest.cpp @@ -0,0 +1,308 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2007 Thiago Macieira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "klocalsocketservertest.h" +#include +#include + +#include +#include +#include "klocalsocket.h" + +static const char afile[] = "/tmp/afile"; +static const char asocket[] = "/tmp/asocket"; + +tst_KLocalSocketServer::tst_KLocalSocketServer() +{ + QFile f(QFile::encodeName(afile)); + f.open(QIODevice::ReadWrite | QIODevice::Truncate); +} + +tst_KLocalSocketServer::~tst_KLocalSocketServer() +{ + QFile::remove(afile); +} + +class TimedConnection: public QThread +{ + Q_OBJECT +public: + ~TimedConnection() + { + wait(); + } +protected: + void run() Q_DECL_OVERRIDE { + KLocalSocket socket; + QThread::usleep(200); + socket.connectToPath(asocket); + socket.waitForConnected(); + } +}; + +void tst_KLocalSocketServer::cleanup() +{ + QFile::remove(asocket); +} + +void tst_KLocalSocketServer::listen_data() +{ + QTest::addColumn("path"); + QTest::addColumn("success"); + + QTest::newRow("null") << QString() << false; + QTest::newRow("empty") << "" << false; + QTest::newRow("a-dir") << "/tmp/" << false; + QTest::newRow("not-a-dir") << QString(afile + QLatin1String("/foo")) << false; + QTest::newRow("not-permitted") << "/root/foo" << false; + QTest::newRow("valid") << asocket << true; +} + +void tst_KLocalSocketServer::listen() +{ + QFETCH(QString, path); + KLocalSocketServer server; + QTEST(server.listen(path), "success"); +} + +void tst_KLocalSocketServer::waitForConnection() +{ + KLocalSocketServer server; + QVERIFY(server.listen(asocket)); + QVERIFY(!server.hasPendingConnections()); + + { + KLocalSocket socket; + socket.connectToPath(asocket); + QVERIFY(socket.waitForConnected()); + + // make sure we can accept that connection + QVERIFY(server.waitForNewConnection()); + QVERIFY(server.hasPendingConnections()); + delete server.nextPendingConnection(); + } + + // test a timeout now + QVERIFY(!server.hasPendingConnections()); + QVERIFY(!server.waitForNewConnection(0)); + QVERIFY(!server.waitForNewConnection(200)); + + { + // now try a timed connection + TimedConnection conn; + conn.start(); + QVERIFY(server.waitForNewConnection(500)); + QVERIFY(server.hasPendingConnections()); + delete server.nextPendingConnection(); + } +} + +void tst_KLocalSocketServer::newConnection() +{ + KLocalSocketServer server; + QVERIFY(server.listen(asocket)); + QVERIFY(!server.hasPendingConnections()); + + // catch the signal + QSignalSpy spy(&server, SIGNAL(newConnection())); + + KLocalSocket socket; + socket.connectToPath(asocket); + QVERIFY(socket.waitForConnected()); + + // let the events be processed + QTest::qWait(100); + + QVERIFY(spy.count() == 1); +} + +void tst_KLocalSocketServer::accept() +{ + KLocalSocketServer server; + QVERIFY(server.listen(asocket)); + QVERIFY(!server.hasPendingConnections()); + + KLocalSocket socket; + socket.connectToPath(asocket); + QVERIFY(socket.waitForConnected()); + QVERIFY(server.waitForNewConnection()); + QVERIFY(server.hasPendingConnections()); + + KLocalSocket *socket2 = server.nextPendingConnection(); + QVERIFY(!server.hasPendingConnections()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(socket2->state(), QAbstractSocket::ConnectedState); + + delete socket2; +} + +void tst_KLocalSocketServer::state() +{ + KLocalSocketServer server; + + // sanity check of the initial state: + QVERIFY(!server.isListening()); + QVERIFY(server.localPath().isEmpty()); + QCOMPARE(int(server.localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); + QVERIFY(!server.hasPendingConnections()); + QVERIFY(!server.nextPendingConnection()); + + // it's not connected, so it shouldn't change timedOut + bool timedOut = true; + QVERIFY(!server.waitForNewConnection(0, &timedOut)); + QVERIFY(timedOut); + timedOut = false; + QVERIFY(!server.waitForNewConnection(0, &timedOut)); + QVERIFY(!timedOut); + + // start listening: + QVERIFY(server.listen(asocket)); + QVERIFY(server.isListening()); + QCOMPARE(server.localPath(), QString(asocket)); + QCOMPARE(int(server.localSocketType()), int(KLocalSocket::UnixSocket)); + QVERIFY(!server.hasPendingConnections()); + QVERIFY(!server.nextPendingConnection()); + + // it must timeout now: + timedOut = false; + QVERIFY(!server.waitForNewConnection(0, &timedOut)); + QVERIFY(timedOut); + + // make a connection: + KLocalSocket socket; + socket.connectToPath(asocket); + QVERIFY(socket.waitForConnected()); + + // it mustn't time out now: + timedOut = true; + QVERIFY(server.waitForNewConnection(0, &timedOut)); + QVERIFY(!timedOut); + + QVERIFY(server.hasPendingConnections()); + KLocalSocket *socket2 = server.nextPendingConnection(); + QVERIFY(socket2); + delete socket2; + + // close: + server.close(); + + // verify state: + QVERIFY(!server.isListening()); + QVERIFY(server.localPath().isEmpty()); + QCOMPARE(int(server.localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); + QVERIFY(!server.hasPendingConnections()); + QVERIFY(!server.nextPendingConnection()); +} + +void tst_KLocalSocketServer::setMaxPendingConnections() +{ + KLocalSocketServer server; + QVERIFY(server.listen(asocket)); + QVERIFY(!server.hasPendingConnections()); + server.setMaxPendingConnections(0); // we don't want to receive + + // check if the event loop won't cause a connection to accepted + KLocalSocket socket; + socket.connectToPath(asocket); + QTest::qWait(100); // 100 ms doing absolutely nothing + QVERIFY(!server.hasPendingConnections()); + + // now check if we get that conenction + server.setMaxPendingConnections(1); + QTest::qWait(100); + QVERIFY(server.hasPendingConnections()); + delete server.nextPendingConnection(); + QVERIFY(socket.waitForDisconnected()); + + // check if we receive only one of the two pending connections + KLocalSocket socket2; + socket.connectToPath(asocket); + socket2.connectToPath(asocket); + QTest::qWait(100); + + QVERIFY(server.hasPendingConnections()); + delete server.nextPendingConnection(); + QVERIFY(!server.hasPendingConnections()); + QVERIFY(!server.nextPendingConnection()); +} + +void tst_KLocalSocketServer::abstractUnixSocket_data() +{ + QTest::addColumn("path"); + QTest::addColumn("success"); + + QTest::newRow("null") << QString() << false; + QTest::newRow("empty") << "" << false; +#if 0 + // apparently, we are allowed to put sockets there, even if we don't have permission to + QTest::newRow("a-dir") << "/tmp/" << false; + QTest::newRow("not-a-dir") << afile + QLatin1String("/foo") << false; + QTest::newRow("not-permitted") << "/root/foo" << false; +#endif + QTest::newRow("valid") << asocket << true; +} + +void tst_KLocalSocketServer::abstractUnixSocket() +{ + QFETCH(QString, path); + QFETCH(bool, success); + + if (success) { + QVERIFY(!QFile::exists(path)); + } + + KLocalSocketServer server; + QCOMPARE(server.listen(path, KLocalSocket::AbstractUnixSocket), success); + + if (success) { + // the socket must not exist in the filesystem + QVERIFY(!QFile::exists(path)); + + // now try to connect to it + KLocalSocket socket; + socket.connectToPath(path, KLocalSocket::AbstractUnixSocket); + QVERIFY(socket.waitForConnected(100)); + QVERIFY(server.waitForNewConnection(100)); + QVERIFY(server.hasPendingConnections()); + + // the socket must still not exist in the filesystem + QVERIFY(!QFile::exists(path)); + + // verify that they can exchange data too: + KLocalSocket *socket2 = server.nextPendingConnection(); + QByteArray data("Hello"); + socket2->write(data); + QVERIFY(socket2->bytesToWrite() == 0 || socket2->waitForBytesWritten(100)); + QVERIFY(socket.waitForReadyRead(100)); + QCOMPARE(socket.read(data.length()), data); + + socket.write(data); + QVERIFY(socket.bytesToWrite() == 0 || socket.waitForBytesWritten(100)); + QVERIFY(socket2->waitForReadyRead(100)); + QCOMPARE(socket2->read(data.length()), data); + + delete socket2; + QVERIFY(socket.waitForDisconnected(100)); + } +} + +QTEST_MAIN(tst_KLocalSocketServer) + +#include "klocalsocketservertest.moc" \ No newline at end of file diff --git a/autotests/klocalsocketservertest.h b/autotests/klocalsocketservertest.h new file mode 100644 index 0000000..748fa65 --- /dev/null +++ b/autotests/klocalsocketservertest.h @@ -0,0 +1,51 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2007 Thiago Macieira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef KLOCALSOCKETSERVERTEST_H +#define KLOCALSOCKETSERVERTEST_H + +#include + +class tst_KLocalSocketServer : public QObject +{ + Q_OBJECT +public: + tst_KLocalSocketServer(); + ~tst_KLocalSocketServer(); + +private Q_SLOTS: + void cleanup(); + + void listen_data(); + void listen(); + + void waitForConnection(); + void newConnection(); + + void accept(); + + void state(); + + void setMaxPendingConnections(); + + void abstractUnixSocket_data(); + void abstractUnixSocket(); +}; + +#endif diff --git a/autotests/klocalsockettest.cpp b/autotests/klocalsockettest.cpp new file mode 100644 index 0000000..17bbf09 --- /dev/null +++ b/autotests/klocalsockettest.cpp @@ -0,0 +1,249 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2007 Thiago Macieira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "klocalsockettest.h" + +#include + +#include +#include +#include +#include "klocalsocket.h" + +static const char socketpath[] = "/tmp/testsocket"; + +tst_KLocalSocket::tst_KLocalSocket() +{ + server = 0; + QFile::remove(QFile::decodeName(socketpath)); +} + +tst_KLocalSocket::~tst_KLocalSocket() +{ + delete server; + QFile::remove(QFile::decodeName(socketpath)); +} + +#include + +class TimedTest: public QThread +{ + Q_OBJECT +public: + KLocalSocket *socket; + TimedTest(KLocalSocket *s) + : socket(s) + { } + ~TimedTest() + { + wait(1000); + } + + void run() Q_DECL_OVERRIDE { + QThread::usleep(100000); + socket->write("Hello, World!", 13); + socket->waitForBytesWritten(); + QThread::usleep(100000); + socket->close(); + delete socket; + } +}; + +void tst_KLocalSocket::initTestCase() +{ + server = new KLocalSocketServer(this); + QVERIFY(server->listen(socketpath)); +} + +void tst_KLocalSocket::connection_data() +{ + QTest::addColumn("path"); + + QTest::newRow("null-path") << QString(); + QTest::newRow("empty-path") << ""; + QTest::newRow("directory") << "/tmp"; + QTest::newRow("directory2") << "/tmp/"; + QTest::newRow("non-existing") << "/tmp/nonexistingsocket"; + QTest::newRow("real") << socketpath; +} + +void tst_KLocalSocket::connection() +{ + QFETCH(QString, path); + KLocalSocket socket; + socket.connectToPath(path); + + bool shouldSucceed = path == socketpath; + QCOMPARE(socket.waitForConnected(1000), shouldSucceed); + if (shouldSucceed) { + QVERIFY(server->waitForNewConnection()); + delete server->nextPendingConnection(); + } else { + qDebug() << socket.errorString(); + } +} + +void tst_KLocalSocket::waitFor() +{ + KLocalSocket socket; + socket.connectToPath(socketpath); + QVERIFY(socket.waitForConnected(1000)); + QVERIFY(server->waitForNewConnection()); + + // now accept: + KLocalSocket *socket2 = server->nextPendingConnection(); + + // start thread: + TimedTest thr(socket2); + socket2->setParent(0); + socket2->moveToThread(&thr); + thr.start(); + + QVERIFY(socket.waitForReadyRead(500)); + QByteArray data = socket.read(512); + + QVERIFY(socket.waitForDisconnected(500)); +} + +void tst_KLocalSocket::reading() +{ + static const char data1[] = "Hello ", + data2[] = "World"; + KLocalSocket socket; + socket.connectToPath(socketpath); + QVERIFY(socket.waitForConnected(1000)); + QVERIFY(server->waitForNewConnection()); + + // now accept and write something: + KLocalSocket *socket2 = server->nextPendingConnection(); + socket2->write(data1, sizeof data1 - 1); + QVERIFY(socket2->bytesToWrite() == 0 || socket2->waitForBytesWritten(200)); + + QVERIFY(socket.waitForReadyRead(200)); + QByteArray read = socket.read(sizeof data1 - 1); + QCOMPARE(read.length(), int(sizeof data1) - 1); + QCOMPARE(read.constData(), data1); + + // write data2 + socket2->write(data2, sizeof data2 - 1); + QVERIFY(socket2->bytesToWrite() == 0 || socket2->waitForBytesWritten(200)); + QVERIFY(socket.waitForReadyRead(200)); + read = socket.read(sizeof data2 - 1); + QCOMPARE(read.length(), int(sizeof data2) - 1); + QCOMPARE(read.constData(), data2); + + delete socket2; +} + +void tst_KLocalSocket::writing() +{ + static const char data1[] = "Hello ", + data2[] = "World"; + KLocalSocket socket; + socket.connectToPath(socketpath); + QVERIFY(socket.waitForConnected(1000)); + QVERIFY(server->waitForNewConnection()); + + // now accept and write something: + KLocalSocket *socket2 = server->nextPendingConnection(); + + QCOMPARE(socket.write(data1, sizeof data1 - 1), Q_INT64_C(sizeof data1 - 1)); + QVERIFY(socket.bytesToWrite() == 0 || socket.waitForBytesWritten(100)); + QVERIFY(socket2->waitForReadyRead()); + + QByteArray read = socket2->read(sizeof data1 - 1); + QCOMPARE(read.length(), int(sizeof data1) - 1); + QCOMPARE(read.constData(), data1); + + // write data2 + QCOMPARE(socket.write(data2, sizeof data2 - 1), Q_INT64_C(sizeof data2 - 1)); + QVERIFY(socket.bytesToWrite() == 0 || socket.waitForBytesWritten(100)); + QVERIFY(socket2->waitForReadyRead()); + read = socket2->read(sizeof data2 - 1); + QCOMPARE(read.length(), int(sizeof data2) - 1); + QCOMPARE(read.constData(), data2); + + delete socket2; +} + +void tst_KLocalSocket::state() +{ + KLocalSocket socket; + + // sanity check: + QCOMPARE(int(socket.localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); + QVERIFY(socket.localPath().isEmpty()); + QVERIFY(socket.peerPath().isEmpty()); + QCOMPARE(int(socket.state()), int(QAbstractSocket::UnconnectedState)); + + // now connect and accept + socket.connectToPath(socketpath); + QVERIFY(socket.waitForConnected(1000)); + QVERIFY(server->waitForNewConnection()); + KLocalSocket *socket2 = server->nextPendingConnection(); + + QCOMPARE(socket.peerPath(), QString(socketpath)); + QCOMPARE(socket2->localPath(), QString(socketpath)); + QCOMPARE(int(socket.state()), int(QAbstractSocket::ConnectedState)); + QCOMPARE(int(socket2->state()), int(QAbstractSocket::ConnectedState)); + QCOMPARE(int(socket.localSocketType()), int(KLocalSocket::UnixSocket)); + QCOMPARE(int(socket2->localSocketType()), int(KLocalSocket::UnixSocket)); + + // now close one of the sockets: + socket.close(); + + // it must have reset its state: + QCOMPARE(int(socket.localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); + QVERIFY(socket.peerPath().isEmpty()); + QCOMPARE(int(socket.state()), int(QAbstractSocket::UnconnectedState)); + + // but the other one mustn't have yet: + QCOMPARE(int(socket2->state()), int(QAbstractSocket::ConnectedState)); + QVERIFY(!socket2->localPath().isEmpty()); + QCOMPARE(int(socket2->localSocketType()), int(KLocalSocket::UnixSocket)); + + // wait for disconnected: + QVERIFY(socket2->waitForDisconnected()); + + // now it must have: + QCOMPARE(int(socket2->state()), int(QAbstractSocket::UnconnectedState)); + QVERIFY(socket2->localPath().isEmpty()); + QCOMPARE(int(socket2->localSocketType()), int(KLocalSocket::UnknownLocalSocketType)); + + delete socket2; +} + +void tst_KLocalSocket::connected() +{ + KLocalSocket socket; + socket.connectToPath(socketpath); + QEXPECT_FAIL("", "Will fix later", Continue); + QVERIFY(!socket.isOpen()); + + QSignalSpy spy(&socket, SIGNAL(connected())); + QTest::qWait(100); + + QEXPECT_FAIL("", "Will fix later", Continue); + QCOMPARE(spy.count(), 1); +} + +QTEST_MAIN(tst_KLocalSocket) + +#include "klocalsockettest.moc" \ No newline at end of file diff --git a/autotests/klocalsockettest.h b/autotests/klocalsockettest.h new file mode 100644 index 0000000..a76bf9e --- /dev/null +++ b/autotests/klocalsockettest.h @@ -0,0 +1,48 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2007 Thiago Macieira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef KLOCALSOCKETTEST_H +#define KLOCALSOCKETTEST_H + +#include + +class KLocalSocketServer; +class tst_KLocalSocket : public QObject +{ + Q_OBJECT +public: + KLocalSocketServer *server; + tst_KLocalSocket(); + ~tst_KLocalSocket(); + +private Q_SLOTS: + void initTestCase(); + void connection_data(); + void connection(); + void waitFor(); + + void reading(); + void writing(); + + void state(); + + void connected(); +}; + +#endif diff --git a/autotests/kmountpointtest.cpp b/autotests/kmountpointtest.cpp new file mode 100644 index 0000000..35fc2a3 --- /dev/null +++ b/autotests/kmountpointtest.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2006 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kmountpointtest.h" + +#include +#include "kmountpoint.h" +#include +#include + +QTEST_MAIN(KMountPointTest) + +void KMountPointTest::initTestCase() +{ + +} + +void KMountPointTest::testCurrentMountPoints() +{ + const KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::NeedRealDeviceName); + if (mountPoints.isEmpty()) { // can happen in chroot jails + QSKIP("mtab is empty"); + return; + } + KMountPoint::Ptr mountWithDevice; + foreach (KMountPoint::Ptr mountPoint, mountPoints) { + qDebug() << "Mount: " << mountPoint->mountedFrom() + << " (" << mountPoint->realDeviceName() << ") " + << mountPoint->mountPoint() << " " << mountPoint->mountType() << endl; + QVERIFY(!mountPoint->mountedFrom().isEmpty()); + QVERIFY(!mountPoint->mountPoint().isEmpty()); + QVERIFY(!mountPoint->mountType().isEmpty()); + // old bug, happened because KMountPoint called KStandardDirs::realPath instead of realFilePath + if (mountPoint->realDeviceName().startsWith(QLatin1String("/dev"))) { // skip this check for cifs mounts for instance + QVERIFY(!mountPoint->realDeviceName().endsWith('/')); + } + + // keep one (any) mountpoint with a device name for the test below + if (!mountPoint->realDeviceName().isEmpty() && !mountWithDevice) { + mountWithDevice = mountPoint; + } + } + + if (!mountWithDevice) { + // This happens on build.kde.org (LXC virtualization, mtab points to non-existing device paths) + qWarning() << "Couldn't find any mountpoint with a valid device?"; + } else { + // Check findByDevice + KMountPoint::Ptr found = mountPoints.findByDevice(mountWithDevice->mountedFrom()); + QVERIFY(found); + QCOMPARE(found->mountPoint(), mountWithDevice->mountPoint()); + found = mountPoints.findByDevice(QStringLiteral("/I/Dont/Exist")); // krazy:exclude=spelling + QVERIFY(!found); + } + + // Check findByPath +#ifdef Q_OS_UNIX + const KMountPoint::Ptr rootMountPoint = mountPoints.findByPath(QStringLiteral("/")); + QVERIFY(rootMountPoint); + QCOMPARE(rootMountPoint->mountPoint(), QStringLiteral("/")); + QVERIFY(!rootMountPoint->probablySlow()); + + QT_STATBUF rootStatBuff; + QCOMPARE(QT_STAT("/", &rootStatBuff), 0); + QT_STATBUF homeStatBuff; + if (QT_STAT("/home", &homeStatBuff) == 0) { + bool sameDevice = rootStatBuff.st_dev == homeStatBuff.st_dev; + const KMountPoint::Ptr homeMountPoint = mountPoints.findByPath(QStringLiteral("/home")); + QVERIFY(homeMountPoint); + //qDebug() << "Checking the home mount point, sameDevice=" << sameDevice; + if (sameDevice) { + QCOMPARE(homeMountPoint->mountPoint(), QStringLiteral("/")); + } else { + QCOMPARE(homeMountPoint->mountPoint(), QStringLiteral("/home")); + } + } else { + qDebug() << "/home doesn't seem to exist, skipping test"; + } +#endif +} + +void KMountPointTest::testPossibleMountPoints() +{ + const KMountPoint::List mountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedRealDeviceName | KMountPoint::NeedMountOptions); + if (mountPoints.isEmpty()) { // can happen in chroot jails + QSKIP("fstab is empty"); + return; + } + KMountPoint::Ptr mountWithDevice; + foreach (KMountPoint::Ptr mountPoint, mountPoints) { + qDebug() << "Possible mount: " << mountPoint->mountedFrom() + << " (" << mountPoint->realDeviceName() << ") " + << mountPoint->mountPoint() << " " << mountPoint->mountType() + << " options:" << mountPoint->mountOptions() << endl; + QVERIFY(!mountPoint->mountedFrom().isEmpty()); + QVERIFY(!mountPoint->mountPoint().isEmpty()); + QVERIFY(!mountPoint->mountType().isEmpty()); + QVERIFY(!mountPoint->mountOptions().isEmpty()); + // old bug, happened because KMountPoint called KStandardDirs::realPath instead of realFilePath + QVERIFY(!mountPoint->realDeviceName().endsWith('/')); + + // keep one (any) mountpoint with a device name for the test below + if (!mountPoint->realDeviceName().isEmpty()) { + mountWithDevice = mountPoint; + } + } + + QVERIFY(mountWithDevice); + +#ifdef Q_OS_UNIX + const KMountPoint::Ptr rootMountPoint = mountPoints.findByPath(QStringLiteral("/")); + QVERIFY(rootMountPoint); + QCOMPARE(rootMountPoint->mountPoint(), QStringLiteral("/")); + QVERIFY(rootMountPoint->realDeviceName().startsWith(QLatin1String("/"))); // Usually /dev, but can be /host/ubuntu/disks/root.disk... + QVERIFY(!rootMountPoint->mountOptions().contains(QStringLiteral("noauto"))); // how would this work? + QVERIFY(!rootMountPoint->probablySlow()); +#endif +} + diff --git a/autotests/kmountpointtest.h b/autotests/kmountpointtest.h new file mode 100644 index 0000000..73fb015 --- /dev/null +++ b/autotests/kmountpointtest.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2006 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef KMOUNTPOINTTEST_H +#define KMOUNTPOINTTEST_H + +#include + +class KMountPointTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + + void testCurrentMountPoints(); + void testPossibleMountPoints(); + +private: +}; + +#endif diff --git a/autotests/knewfilemenutest.cpp b/autotests/knewfilemenutest.cpp new file mode 100644 index 0000000..4138f44 --- /dev/null +++ b/autotests/knewfilemenutest.cpp @@ -0,0 +1,194 @@ +/* This file is part of the KDE libraries + Copyright (c) 2012 David Faure + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef Q_OS_UNIX +#include +#include +#endif + +class KNewFileMenuTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase() + { + qputenv("KDE_FORK_SLAVES", "yes"); // to avoid a runtime dependency on klauncher +#ifdef Q_OS_UNIX + m_umask = ::umask(0); + ::umask(m_umask); +#endif + QVERIFY(m_tmpDir.isValid()); + } + + void cleanupTestCase() + { + } + + // Ensure that we can use storedPut() with a qrc file as input + // similar to JobTest::storedPutIODeviceFile, but with a qrc file as input + // (and here because jobtest doesn't link to KIO::FileWidgets, which has the qrc) + void storedPutIODeviceQrcFile() + { + // Given a source (in a Qt resource file) and a destination file + const QString src = ":/kio5/newfile-templates/.source/HTMLFile.html"; + QVERIFY(QFile::exists(src)); + QFile srcFile(src); + QVERIFY(srcFile.open(QIODevice::ReadOnly)); + const QString dest = m_tmpDir.path() + "/dest"; + QFile::remove(dest); + const QUrl destUrl = QUrl::fromLocalFile(dest); + + // When using storedPut with the QFile as argument + KIO::StoredTransferJob *job = KIO::storedPut(&srcFile, destUrl, -1, KIO::Overwrite | KIO::HideProgressInfo); + + // Then the copy should succeed and the dest file exist + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QVERIFY(QFile::exists(dest)); + QCOMPARE(QFileInfo(src).size(), QFileInfo(dest).size()); + // And the permissions should respect the umask (#359581) +#ifdef Q_OS_UNIX + if (m_umask & S_IWOTH) { + QVERIFY2(!(QFileInfo(dest).permissions() & QFileDevice::WriteOther), qPrintable(dest)); + } + if (m_umask & S_IWGRP) { + QVERIFY(!(QFileInfo(dest).permissions() & QFileDevice::WriteGroup)); + } +#endif + QFile::remove(dest); + } + + void test_data() + { + QTest::addColumn("actionText"); // the action we're clicking on + QTest::addColumn("expectedDefaultFilename"); // the initial filename in the dialog + QTest::addColumn("typedFilename"); // what the user is typing + QTest::addColumn("expectedFilename"); // the final file name + + QTest::newRow("text file") << "Text File" << "Text File" << "tmp_knewfilemenutest.txt" << "tmp_knewfilemenutest.txt"; + QTest::newRow("text file with jpeg extension") << "Text File" << "Text File" << "foo.jpg" << "foo.jpg.txt"; + QTest::newRow("html file") << "HTML File" << "HTML File" << "foo.html" << "foo.html"; + QTest::newRow("url desktop file") << "Link to Location " << "" << "tmp_link.desktop" << "tmp_link.desktop"; + QTest::newRow("url desktop file no extension") << "Link to Location " << "" << "tmp_link" << "tmp_link"; + QTest::newRow("url desktop file .pl extension") << "Link to Location " << "" << "tmp_link.pl" << "tmp_link.pl.desktop"; + QTest::newRow("symlink") << "Basic link" << "" << "thelink" << "thelink"; + QTest::newRow("folder") << "Folder..." << "New Folder" << "folder1" << "folder1"; + QTest::newRow("folder_default_name") << "Folder..." << "New Folder" << "New Folder" << "New Folder"; + QTest::newRow("folder_with_suggested_name") << "Folder..." << "New Folder (1)" << "New Folder" << "New Folder"; + QTest::newRow("application") << "Link to Application..." << "Link to Application" << "app1" << "app1.desktop"; + } + + void test() + { + QFETCH(QString, actionText); + QFETCH(QString, expectedDefaultFilename); + QFETCH(QString, typedFilename); + QFETCH(QString, expectedFilename); + + QWidget parentWidget; + KActionCollection coll(this, QStringLiteral("foo")); + KNewFileMenu menu(&coll, QStringLiteral("the_action"), this); + menu.setModal(false); + menu.setParentWidget(&parentWidget); + QList lst; + lst << QUrl::fromLocalFile(m_tmpDir.path()); + menu.setPopupFiles(lst); + menu.checkUpToDate(); + QAction *action = coll.action(QStringLiteral("the_action")); + QVERIFY(action); + QAction *textAct = 0; + Q_FOREACH (QAction *act, action->menu()->actions()) { + if (act->text().contains(actionText)) { + textAct = act; + } + } + if (!textAct) { + Q_FOREACH (QAction *act, action->menu()->actions()) { + qDebug() << act << act->text() << act->data(); + } + const QString err = "action with text \"" + actionText + "\" not found."; + QVERIFY2(textAct, qPrintable(err)); + } + textAct->trigger(); + QDialog *dialog = parentWidget.findChild(); + QVERIFY(dialog); + if (KNameAndUrlInputDialog *nauiDialog = qobject_cast(dialog)) { + QCOMPARE(nauiDialog->name(), expectedDefaultFilename); + nauiDialog->setSuggestedName(typedFilename); + nauiDialog->setSuggestedUrl(QUrl(QStringLiteral("file:///etc"))); + } else if (KPropertiesDialog *propsDialog = qobject_cast(dialog)) { + QLineEdit *lineEdit = propsDialog->findChild("KFilePropsPlugin::nameLineEdit"); + QVERIFY(lineEdit); + QCOMPARE(lineEdit->text(), expectedDefaultFilename); + lineEdit->setText(typedFilename); + } else { + QLineEdit *lineEdit = dialog->findChild(); + QVERIFY(lineEdit); + QCOMPARE(lineEdit->text(), expectedDefaultFilename); + lineEdit->setText(typedFilename); + } + QUrl emittedUrl; + QSignalSpy spy(&menu, SIGNAL(fileCreated(QUrl))); + QSignalSpy folderSpy(&menu, SIGNAL(directoryCreated(QUrl))); + dialog->accept(); + const QString path = m_tmpDir.path() + '/' + expectedFilename; + if (actionText == QLatin1String("Folder...")) { + QVERIFY(folderSpy.wait(1000)); + emittedUrl = folderSpy.at(0).at(0).toUrl(); + QVERIFY(QFileInfo(path).isDir()); + } else { + if (spy.isEmpty()) { + QVERIFY(spy.wait(1000)); + } + emittedUrl = spy.at(0).at(0).toUrl(); + QVERIFY(QFile::exists(path)); + if (actionText != QLatin1String("Basic link")) { + QFile file(path); + QVERIFY(file.open(QIODevice::ReadOnly)); + const QByteArray contents = file.readAll(); + if (actionText.startsWith("HTML")) { + QCOMPARE(QString::fromLatin1(contents.left(6)), QStringLiteral("")); + } + } + } + QCOMPARE(emittedUrl.toLocalFile(), path); + } +private: + QTemporaryDir m_tmpDir; +#ifdef Q_OS_UNIX + mode_t m_umask; +#endif +}; + +QTEST_MAIN(KNewFileMenuTest) + +#include "knewfilemenutest.moc" diff --git a/autotests/kprotocolinfotest.cpp b/autotests/kprotocolinfotest.cpp new file mode 100644 index 0000000..7ea02b7 --- /dev/null +++ b/autotests/kprotocolinfotest.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2002 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +// Tests both KProtocolInfo and KProtocolManager + +class KProtocolInfoTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + + void testBasic(); + void testExtraFields(); + void testShowFilePreview(); + void testSlaveProtocol(); + void testProxySettings_data(); + void testProxySettings(); + void testCapabilities(); + void testProtocolForArchiveMimetype(); + void testHelperProtocols(); +}; + +void KProtocolInfoTest::initTestCase() +{ + QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/kioslaverc"; + QFile::remove(configFile); +} + +void KProtocolInfoTest::testBasic() +{ + QVERIFY(KProtocolInfo::isKnownProtocol(QUrl(QStringLiteral("http:/")))); + QVERIFY(KProtocolInfo::isKnownProtocol(QUrl(QStringLiteral("file:/")))); + QVERIFY(KProtocolInfo::exec(QStringLiteral("file")).contains(QStringLiteral("kf5/kio/file"))); + QCOMPARE(KProtocolInfo::protocolClass(QStringLiteral("file")), QStringLiteral(":local")); + + QCOMPARE(KProtocolInfo::protocolClass(QStringLiteral("http")), QStringLiteral(":internet")); + + QVERIFY(KProtocolManager::supportsListing(QUrl(QStringLiteral("ftp://10.1.1.10")))); + + const QUrl url = QUrl::fromLocalFile(QStringLiteral("/tmp")); + QCOMPARE(KProtocolManager::inputType(url), KProtocolInfo::T_NONE); + QCOMPARE(KProtocolManager::outputType(url), KProtocolInfo::T_FILESYSTEM); + QVERIFY(KProtocolManager::supportsReading(url)); +} + +void KProtocolInfoTest::testExtraFields() +{ + KProtocolInfo::ExtraFieldList extraFields = KProtocolInfo::extraFields(QUrl(QStringLiteral("trash:/"))); + KProtocolInfo::ExtraFieldList::Iterator extraFieldsIt = extraFields.begin(); + for (; extraFieldsIt != extraFields.end(); ++extraFieldsIt) { + qDebug() << (*extraFieldsIt).name << " " << (*extraFieldsIt).type; + } + // TODO +} + +void KProtocolInfoTest::testShowFilePreview() +{ + QVERIFY(KProtocolInfo::showFilePreview(QStringLiteral("file"))); + QVERIFY(!KProtocolInfo::showFilePreview(QStringLiteral("audiocd"))); +} + +void KProtocolInfoTest::testSlaveProtocol() +{ + QString proxy; + QString protocol = KProtocolManager::slaveProtocol(QUrl(QStringLiteral("http://bugs.kde.org")), proxy); + QCOMPARE(protocol, QStringLiteral("http")); + QVERIFY(!KProtocolManager::useProxy()); + + // Just to test it doesn't deadlock + KProtocolManager::reparseConfiguration(); + protocol = KProtocolManager::slaveProtocol(QUrl(QStringLiteral("http://bugs.kde.org")), proxy); + QCOMPARE(protocol, QStringLiteral("http")); +} + +void KProtocolInfoTest::testProxySettings_data() +{ + QTest::addColumn("proxyType"); + + // Just to test it doesn't deadlock (bug 346214) + QTest::newRow("manual") << static_cast(KProtocolManager::ManualProxy); + QTest::newRow("wpad") << static_cast(KProtocolManager::WPADProxy); + // Same for bug 350890 + QTest::newRow("envvar") << static_cast(KProtocolManager::EnvVarProxy); +} + +void KProtocolInfoTest::testProxySettings() +{ + QFETCH(int, proxyType); + KConfig config(QStringLiteral("kioslaverc"), KConfig::NoGlobals); + KConfigGroup cfg(&config, "Proxy Settings"); + cfg.writeEntry("ProxyType", proxyType); + cfg.sync(); + KProtocolManager::reparseConfiguration(); + QString proxy; + QString protocol = KProtocolManager::slaveProtocol(QUrl(QStringLiteral("http://bugs.kde.org")), proxy); + QCOMPARE(protocol, QStringLiteral("http")); + QVERIFY(KProtocolManager::useProxy()); + + // restore + cfg.writeEntry("ProxyType", static_cast(KProtocolManager::NoProxy)); + cfg.sync(); + KProtocolManager::reparseConfiguration(); +} + +void KProtocolInfoTest::testCapabilities() +{ + QStringList capabilities = KProtocolInfo::capabilities(QStringLiteral("imap")); + qDebug() << "kio_imap capabilities: " << capabilities; + //QVERIFY(capabilities.contains("ACL")); +} + +void KProtocolInfoTest::testProtocolForArchiveMimetype() +{ + if (!QFile::exists(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + "zip.protocol"))) { + QSKIP("kdebase not installed"); + } else { + const QString zip = KProtocolManager::protocolForArchiveMimetype(QStringLiteral("application/zip")); + QCOMPARE(zip, QStringLiteral("zip")); + } +} + +void KProtocolInfoTest::testHelperProtocols() +{ + QVERIFY(!KProtocolInfo::isHelperProtocol(QStringLiteral("http"))); + QVERIFY(!KProtocolInfo::isHelperProtocol(QStringLiteral("ftp"))); + QVERIFY(!KProtocolInfo::isHelperProtocol(QStringLiteral("file"))); + QVERIFY(!KProtocolInfo::isHelperProtocol(QStringLiteral("unknown"))); + // Comes from ktelnetservice.desktop:MimeType=x-scheme-handler/telnet;x-scheme-handler/rlogin;x-scheme-handler/ssh; + // TODO: this logic has moved to KRun. Should it be public API, so we can unittest it? + //QVERIFY(KProtocolInfo::isHelperProtocol("telnet")); + + // To test that compat still works + if (KProtocolInfo::isKnownProtocol(QStringLiteral("tel"))) { + QVERIFY(KProtocolInfo::isHelperProtocol(QStringLiteral("tel"))); + } + + // TODO: this logic has moved to KRun. Should it be public API, so we can unittest it? +#if 0 + QVERIFY(KProtocolInfo::isKnownProtocol("mailto")); + QVERIFY(KProtocolInfo::isHelperProtocol("mailto")); + QVERIFY(KProtocolInfo::isHelperProtocol(QUrl("mailto:faure@kde.org"))); + + // "mailto" is associated with kmail2 when present, and with kmailservice otherwise. + KService::Ptr kmail2 = KService::serviceByStorageId("KMail2.desktop"); + if (kmail2) { + //qDebug() << kmail2->entryPath(); + QVERIFY2(KProtocolInfo::exec("mailto").contains(QLatin1String("kmail -caption \"%c\"")), // comes from KMail2.desktop + qPrintable(KProtocolInfo::exec("mailto"))); + } else { + QCOMPARE(KProtocolInfo::exec("mailto"), QLatin1String("kmailservice %u")); + } +#endif +} + +QTEST_MAIN(KProtocolInfoTest) + +#include "kprotocolinfotest.moc" diff --git a/autotests/krununittest.cpp b/autotests/krununittest.cpp new file mode 100644 index 0000000..823336f --- /dev/null +++ b/autotests/krununittest.cpp @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2003 Waldo Bastian + * Copyright (C) 2007, 2009 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#undef QT_USE_FAST_OPERATOR_PLUS +#undef QT_USE_FAST_CONCATENATION + +#include "krununittest.h" + +#include + +QTEST_GUILESS_MAIN(KRunUnitTest) + +#include + +#include "krun.h" +#include +#include +#include +#include +#include +#include +#include +#include "kiotesthelper.h" // createTestFile etc. +#ifdef Q_OS_UNIX +#include // kill +#endif + +void KRunUnitTest::initTestCase() +{ + QStandardPaths::enableTestMode(true); + // testProcessDesktopExec works only if your terminal application is set to "x-term" + KConfigGroup cg(KSharedConfig::openConfig(), "General"); + cg.writeEntry("TerminalApplication", "x-term"); + + // Determine the full path of sh - this is needed to make testProcessDesktopExecNoFile() + // pass on systems where QStandardPaths::findExecutable("sh") is not "/bin/sh". + m_sh = QStandardPaths::findExecutable(QStringLiteral("sh")); + if (m_sh.isEmpty()) { + m_sh = QStringLiteral("/bin/sh"); + } +} + +void KRunUnitTest::cleanupTestCase() +{ + std::for_each(m_filesToRemove.begin(), m_filesToRemove.end(), [](const QString & f) { + QFile::remove(f); + }); +} + +void KRunUnitTest::testBinaryName_data() +{ + QTest::addColumn("execLine"); + QTest::addColumn("removePath"); + QTest::addColumn("expected"); + + QTest::newRow("/usr/bin/ls true") << "/usr/bin/ls" << true << "ls"; + QTest::newRow("/usr/bin/ls false") << "/usr/bin/ls" << false << "/usr/bin/ls"; + QTest::newRow("/path/to/wine \"long argument with path\"") << "/path/to/wine \"long argument with path\"" << true << "wine"; + QTest::newRow("/path/with/a/sp\\ ace/exe arg1 arg2") << "/path/with/a/sp\\ ace/exe arg1 arg2" << true << "exe"; + QTest::newRow("\"progname\" \"arg1\"") << "\"progname\" \"arg1\"" << true << "progname"; + QTest::newRow("'quoted' \"arg1\"") << "'quoted' \"arg1\"" << true << "quoted"; + QTest::newRow(" 'leading space' arg1") << " 'leading space' arg1" << true << "leading space"; +} + +void KRunUnitTest::testBinaryName() +{ + QFETCH(QString, execLine); + QFETCH(bool, removePath); + QFETCH(QString, expected); + if (removePath) { + QCOMPARE(KIO::DesktopExecParser::executableName(execLine), expected); + } else { + QCOMPARE(KIO::DesktopExecParser::executablePath(execLine), expected); + } +} + +//static const char *bt(bool tr) { return tr?"true":"false"; } +static void checkDesktopExecParser(const char *exec, const char *term, const char *sus, + const QList &urls, bool tf, const QString &b) +{ + QFile out(QStringLiteral("kruntest.desktop")); + if (!out.open(QIODevice::WriteOnly)) { + abort(); + } + QByteArray str("[Desktop Entry]\n" + "Type=Application\n" + "Name=just_a_test\n" + "Icon=~/icon.png\n"); + str += QByteArray(exec) + '\n'; + str += QByteArray(term) + '\n'; + str += QByteArray(sus) + '\n'; + out.write(str); + out.close(); + + KService service(QDir::currentPath() + "/kruntest.desktop"); + /*qDebug() << QString().sprintf( + "processDesktopExec( " + "service = {\nexec = %s\nterminal = %s, terminalOptions = %s\nsubstituteUid = %s, user = %s }," + "\nURLs = { %s },\ntemp_files = %s )", + service.exec().toLatin1().constData(), bt(service.terminal()), service.terminalOptions().toLatin1().constData(), bt(service.substituteUid()), service.username().toLatin1().constData(), + KShell::joinArgs(urls.toStringList()).toLatin1().constData(), bt(tf)); + */ + KIO::DesktopExecParser parser(service, urls); + parser.setUrlsAreTempFiles(tf); + QCOMPARE(KShell::joinArgs(parser.resultingArguments()), b); + + QFile::remove(QStringLiteral("kruntest.desktop")); +} + +void KRunUnitTest::testProcessDesktopExec() +{ + QList l0; + static const char *const execs[] = { "Exec=date -u", "Exec=echo $PWD" }; + static const char *const terms[] = { "Terminal=false", "Terminal=true\nTerminalOptions=-T \"%f - %c\"" }; + static const char *const sus[] = { "X-KDE-SubstituteUID=false", "X-KDE-SubstituteUID=true\nX-KDE-Username=sprallo" }; + static const char *const results[] = { + "/bin/date -u", // 0 + "/bin/sh -c 'echo $PWD '", // 1 + "x-term -T ' - just_a_test' -e /bin/date -u", // 2 + "x-term -T ' - just_a_test' -e /bin/sh -c 'echo $PWD '", // 3 + /* kdesu */ " -u sprallo -c '/bin/date -u'", // 4 + /* kdesu */ " -u sprallo -c '/bin/sh -c '\\''echo $PWD '\\'''", // 5 + "x-term -T ' - just_a_test' -e su sprallo -c '/bin/date -u'", // 6 + "x-term -T ' - just_a_test' -e su sprallo -c '/bin/sh -c '\\''echo $PWD '\\'''", // 7 + }; + + // Find out the full path of the shell which will be used to execute shell commands + KProcess process; + process.setShellCommand(QLatin1String("")); + const QString shellPath = process.program().at(0); + + // Arch moved /bin/date to /usr/bin/date... + const QString datePath = QStandardPaths::findExecutable(QStringLiteral("date")); + + for (int su = 0; su < 2; su++) + for (int te = 0; te < 2; te++) + for (int ex = 0; ex < 2; ex++) { + int pt = ex + te * 2 + su * 4; + QString exe; + if (pt == 4 || pt == 5) { + exe = QFile::decodeName(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kdesu"); + if (!QFile::exists(exe)) { + qWarning() << "kdesu not found, skipping test"; + continue; + } + } + const QString result = QString::fromLatin1(results[pt]) + .replace(QLatin1String("/bin/sh"), shellPath) + .replace(QLatin1String("/bin/date"), datePath); + checkDesktopExecParser(execs[ex], terms[te], sus[su], l0, false, exe + result); + } +} + +void KRunUnitTest::testProcessDesktopExecNoFile_data() +{ + QTest::addColumn("execLine"); + QTest::addColumn >("urls"); + QTest::addColumn("tempfiles"); + QTest::addColumn("expected"); + + QList l0; + QList l1; l1 << QUrl(QStringLiteral("file:/tmp")); + QList l2; l2 << QUrl(QStringLiteral("http://localhost/foo")); + QList l3; l3 << QUrl(QStringLiteral("file:/local/some file")) << QUrl(QStringLiteral("http://remotehost.org/bar")); + QList l4; l4 << QUrl(QStringLiteral("http://login:password@www.kde.org")); + + // A real-world use case would be kate. + // But I picked kdeinit5 since it's installed by kdelibs + QString kdeinit = QStandardPaths::findExecutable(QStringLiteral("kdeinit5")); + if (kdeinit.isEmpty()) { + kdeinit = QStringLiteral("kdeinit5"); + } + + QString kioexec = CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioexec"; + QVERIFY(QFile::exists(kioexec)); + + QString kmailservice = QStandardPaths::findExecutable(QStringLiteral("kmailservice5")); + if (!QFile::exists(kmailservice)) { + kmailservice = QStringLiteral("kmailservice5"); + } + + QTest::newRow("%U l0") << "kdeinit5 %U" << l0 << false << kdeinit; + QTest::newRow("%U l1") << "kdeinit5 %U" << l1 << false << kdeinit + " /tmp"; + QTest::newRow("%U l2") << "kdeinit5 %U" << l2 << false << kdeinit + " http://localhost/foo"; + QTest::newRow("%U l3") << "kdeinit5 %U" << l3 << false << kdeinit + " '/local/some file' http://remotehost.org/bar"; + + //QTest::newRow("%u l0") << "kdeinit5 %u" << l0 << false << kdeinit; // gives runtime warning + QTest::newRow("%u l1") << "kdeinit5 %u" << l1 << false << kdeinit + " /tmp"; + QTest::newRow("%u l2") << "kdeinit5 %u" << l2 << false << kdeinit + " http://localhost/foo"; + //QTest::newRow("%u l3") << "kdeinit5 %u" << l3 << false << kdeinit; // gives runtime warning + + QTest::newRow("%F l0") << "kdeinit5 %F" << l0 << false << kdeinit; + QTest::newRow("%F l1") << "kdeinit5 %F" << l1 << false << kdeinit + " /tmp"; + QTest::newRow("%F l2") << "kdeinit5 %F" << l2 << false << kioexec + " 'kdeinit5 %F' http://localhost/foo"; + QTest::newRow("%F l3") << "kdeinit5 %F" << l3 << false << kioexec + " 'kdeinit5 %F' 'file:///local/some file' http://remotehost.org/bar"; + + QTest::newRow("%F l1 tempfile") << "kdeinit5 %F" << l1 << true << kioexec + " --tempfiles 'kdeinit5 %F' file:///tmp"; + QTest::newRow("%f l1 tempfile") << "kdeinit5 %f" << l1 << true << kioexec + " --tempfiles 'kdeinit5 %f' file:///tmp"; + + QTest::newRow("sh -c kdeinit5 %F") << "sh -c \"kdeinit5 \"'\\\"'\"%F\"'\\\"'" + << l1 << false << m_sh + " -c 'kdeinit5 \\\"/tmp\\\"'"; + + QTest::newRow("kmailservice5 %u l1") << "kmailservice5 %u" << l1 << false << kmailservice + " /tmp"; + QTest::newRow("kmailservice5 %u l4") << "kmailservice5 %u" << l4 << false << kmailservice + " http://login:password@www.kde.org"; +} + +void KRunUnitTest::testProcessDesktopExecNoFile() +{ + QFETCH(QString, execLine); + KService service(QStringLiteral("dummy"), execLine, QStringLiteral("app")); + QFETCH(QList, urls); + QFETCH(bool, tempfiles); + QFETCH(QString, expected); + KIO::DesktopExecParser parser(service, urls); + parser.setUrlsAreTempFiles(tempfiles); + QCOMPARE(KShell::joinArgs(parser.resultingArguments()), expected); +} + +class KRunImpl : public KRun +{ +public: + KRunImpl(const QUrl &url) + : KRun(url, 0, false), m_errCode(-1) {} + + void foundMimeType(const QString &type) Q_DECL_OVERRIDE { + m_mimeType = type; + // don't call KRun::foundMimeType, we don't want to start an app ;-) + setFinished(true); + } + + void handleInitError(int kioErrorCode, const QString &err) Q_DECL_OVERRIDE { + m_errCode = kioErrorCode; + m_errText = err; + } + + QString mimeTypeFound() const + { + return m_mimeType; + } + int errorCode() const + { + return m_errCode; + } + QString errorText() const + { + return m_errText; + } + +private: + int m_errCode; + QString m_errText; + QString m_mimeType; +}; + +void KRunUnitTest::testMimeTypeFile() +{ + const QString filePath = homeTmpDir() + "file"; + createTestFile(filePath, true); + KRunImpl *krun = new KRunImpl(QUrl::fromLocalFile(filePath)); + krun->setAutoDelete(false); + QSignalSpy spyFinished(krun, SIGNAL(finished())); + QVERIFY(spyFinished.wait(1000)); + QCOMPARE(krun->mimeTypeFound(), QString::fromLatin1("text/plain")); + delete krun; +} + +void KRunUnitTest::testMimeTypeDirectory() +{ + const QString dir = homeTmpDir() + "dir"; + createTestDirectory(dir); + KRunImpl *krun = new KRunImpl(QUrl::fromLocalFile(dir)); + QSignalSpy spyFinished(krun, SIGNAL(finished())); + QVERIFY(spyFinished.wait(1000)); + QCOMPARE(krun->mimeTypeFound(), QString::fromLatin1("inode/directory")); +} + +void KRunUnitTest::testMimeTypeBrokenLink() +{ + const QString dir = homeTmpDir() + "dir"; + createTestDirectory(dir); + KRunImpl *krun = new KRunImpl(QUrl::fromLocalFile(dir + "/testlink")); + QSignalSpy spyError(krun, SIGNAL(error())); + QSignalSpy spyFinished(krun, SIGNAL(finished())); + QVERIFY(spyFinished.wait(1000)); + QVERIFY(krun->mimeTypeFound().isEmpty()); + QCOMPARE(spyError.count(), 1); + QCOMPARE(krun->errorCode(), int(KIO::ERR_DOES_NOT_EXIST)); + QVERIFY(krun->errorText().contains("does not exist")); + QTest::qWait(100); // let auto-deletion proceed. +} + +void KRunUnitTest::testMimeTypeDoesNotExist() +{ + KRunImpl *krun = new KRunImpl(QUrl::fromLocalFile(QStringLiteral("/does/not/exist"))); + QSignalSpy spyError(krun, SIGNAL(error())); + QSignalSpy spyFinished(krun, SIGNAL(finished())); + QVERIFY(spyFinished.wait(1000)); + QVERIFY(krun->mimeTypeFound().isEmpty()); + QCOMPARE(spyError.count(), 1); + QTest::qWait(100); // let auto-deletion proceed. +} + +static const char s_tempServiceName[] = "krununittest_service.desktop"; + +static void createSrcFile(const QString path) +{ + QFile srcFile(path); + QVERIFY2(srcFile.open(QFile::WriteOnly), qPrintable(srcFile.errorString())); + srcFile.write("Hello world\n"); +} + +void KRunUnitTest::KRunRunService_data() +{ + QTest::addColumn("tempFile"); + QTest::addColumn("useRunApplication"); + + QTest::newRow("standard") << false << false; + QTest::newRow("tempfile") << true << false; + QTest::newRow("runApp") << false << true; + QTest::newRow("runApp_tempfile") << true << true; +} +void KRunUnitTest::KRunRunService() +{ + QFETCH(bool, tempFile); + QFETCH(bool, useRunApplication); + + // Given a service desktop file and a source file + const QString path = createTempService(); + //KService::Ptr service = KService::serviceByDesktopPath(s_tempServiceName); + //QVERIFY(service); + KService service(path); + QTemporaryDir tempDir; + const QString srcDir = tempDir.path(); + const QString srcFile = srcDir + "/srcfile"; + createSrcFile(srcFile); + QVERIFY(QFile::exists(srcFile)); + QList urls; + urls.append(QUrl::fromLocalFile(srcFile)); + + // When calling KRun::runService or KRun::runApplication + qint64 pid = useRunApplication + ? KRun::runApplication(service, urls, 0, tempFile ? KRun::RunFlags(KRun::DeleteTemporaryFiles) : KRun::RunFlags()) + : KRun::runService(service, urls, 0, tempFile); + + // Then the service should be executed (which copies the source file to "dest") + QVERIFY(pid != 0); + const QString dest = srcDir + "/dest"; + QTRY_VERIFY(QFile::exists(dest)); + QVERIFY(QFile::exists(srcFile)); // if tempfile is true, kioexec will delete it... in 3 minutes. + + // All done, clean up. + QVERIFY(QFile::remove(dest)); +#ifdef Q_OS_UNIX + ::kill(pid, SIGTERM); +#endif +} + +QString KRunUnitTest::createTempService() +{ + // fakeservice: deleted and recreated by testKSycocaUpdate, don't use in other tests + const QString fileName = s_tempServiceName; + //bool mustUpdateKSycoca = !KService::serviceByDesktopPath(fileName); + const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + fileName; + if (!QFile::exists(fakeService)) { + //mustUpdateKSycoca = true; + KDesktopFile file(fakeService); + KConfigGroup group = file.desktopGroup(); + group.writeEntry("Name", "KRunUnittestService"); + group.writeEntry("Type", "Service"); +#ifdef Q_OS_WIN + group.writeEntry("Exec", "copy.exe %f %d/dest"); +#else + group.writeEntry("Exec", "cp %f %d/dest"); +#endif + file.sync(); + QFile f(fakeService); + f.setPermissions(f.permissions() | QFile::ExeOwner | QFile::ExeUser); + } + m_filesToRemove.append(fakeService); + return fakeService; +} diff --git a/autotests/krununittest.h b/autotests/krununittest.h new file mode 100644 index 0000000..1f138c1 --- /dev/null +++ b/autotests/krununittest.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005, 2009 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KRUNUNITTEST_H +#define KRUNUNITTEST_H + +#include +#include + +class KRunUnitTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testBinaryName_data(); + void testBinaryName(); + void testProcessDesktopExec(); + void testProcessDesktopExecNoFile_data(); + void testProcessDesktopExecNoFile(); + + void testMimeTypeFile(); + void testMimeTypeDirectory(); + void testMimeTypeBrokenLink(); + void testMimeTypeDoesNotExist(); + + void KRunRunService_data(); + void KRunRunService(); +private: + QString createTempService(); + + QString m_sh; + QStringList m_filesToRemove; + +}; + +#endif /* KRUNUNITTEST_H */ + diff --git a/autotests/ktcpsockettest.cpp b/autotests/ktcpsockettest.cpp new file mode 100644 index 0000000..b9ee21e --- /dev/null +++ b/autotests/ktcpsockettest.cpp @@ -0,0 +1,401 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2007 Andreas Hartmetz + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ktcpsockettest.h" +#include +#include +#include +#include "ktcpsocket.h" + +/* TODO items: + - test errors including error strings + - test overriding errors + - test the most important SSL operations (full coverage is very hard) + - test readLine() + - test nonblocking, signal based usage + - test that waitForDisconnected() writes out all buffered data + - (test local and peer address and port getters) + - test isValid(). Its documentation is less than clear :( + */ + +static const quint16 testPort = 22342; + +class ServerThread : public QThread +{ + Q_OBJECT +public: + Server *volatile server; + ServerThread() + : server(0) {} + ~ServerThread() + { + wait(100); + } +protected: + void run() Q_DECL_OVERRIDE; +}; + +KTcpSocketTest::KTcpSocketTest() +{ + server = 0; +} + +KTcpSocketTest::~KTcpSocketTest() +{ + delete server; +} + +void KTcpSocketTest::invokeOnServer(const char *method) +{ + QMetaObject::invokeMethod(server, method, Qt::QueuedConnection); + QTest::qWait(1); //Enter the event loop +} + +void ServerThread::run() +{ + server = new Server(testPort); + exec(); //Start the event loop; this won't return. +} + +Server::Server(quint16 _port) + : listener(0), + socket(0), + port(_port) +{ + listener = new QTcpServer(); + listener->listen(QHostAddress(QStringLiteral("127.0.0.1")), testPort); +} + +void Server::cleanupSocket() +{ + Q_ASSERT(socket); + socket->close(); + socket->deleteLater(); + socket = 0; +} + +void KTcpSocketTest::initTestCase() +{ + ServerThread *st = new ServerThread(); + st->start(); + while (!st->server) + ; + //Let the other thread initialize its event loop or whatever; there were problems... + QTest::qWait(200); + server = st->server; +} + +void KTcpSocketTest::connectDisconnect() +{ + invokeOnServer("connectDisconnect"); + + KTcpSocket *s = new KTcpSocket(this); + QCOMPARE(s->openMode(), QIODevice::NotOpen); + QCOMPARE(s->error(), KTcpSocket::UnknownError); + + s->connectToHost(QStringLiteral("127.0.0.1"), testPort); + QCOMPARE(s->state(), KTcpSocket::ConnectingState); + QVERIFY(s->openMode() & QIODevice::ReadWrite); + const bool connected = s->waitForConnected(150); + QVERIFY(connected); + QCOMPARE(s->state(), KTcpSocket::ConnectedState); + + s->waitForDisconnected(150); + //ClosingState occurs only when there is buffered data + QCOMPARE(s->state(), KTcpSocket::UnconnectedState); + + s->deleteLater(); +} + +void Server::connectDisconnect() +{ + listener->waitForNewConnection(10000, 0); + socket = listener->nextPendingConnection(); + + cleanupSocket(); +} + +#define TESTDATA QByteArray("things and stuff and a bag of chips") + +void KTcpSocketTest::read() +{ + invokeOnServer("read"); + + KTcpSocket *s = new KTcpSocket(this); + s->connectToHost(QStringLiteral("127.0.0.1"), testPort); + s->waitForConnected(40); + s->waitForReadyRead(40); + QCOMPARE((int)s->bytesAvailable(), TESTDATA.size()); + QCOMPARE(s->readAll(), TESTDATA); + s->deleteLater(); +} + +void Server::read() +{ + listener->waitForNewConnection(10000, 0); + socket = listener->nextPendingConnection(); + + socket->write(TESTDATA); + socket->waitForBytesWritten(150); + cleanupSocket(); +} + +void KTcpSocketTest::write() +{ + invokeOnServer("write"); + + KTcpSocket *s = new KTcpSocket(this); + s->connectToHost(QStringLiteral("127.0.0.1"), testPort); + s->waitForConnected(40); + s->write(TESTDATA); + QCOMPARE((int)s->bytesToWrite(), TESTDATA.size()); + s->waitForReadyRead(150); + QCOMPARE((int)s->bytesAvailable(), TESTDATA.size()); + QCOMPARE(s->readAll(), TESTDATA); + + s->write(TESTDATA); + QCOMPARE((int)s->bytesToWrite(), TESTDATA.size()); + s->disconnectFromHost(); + //Test closing with pending data to transmit (pending rx data comes later) + QCOMPARE(s->state(), KTcpSocket::ClosingState); + s->waitForDisconnected(150); + QCOMPARE(s->state(), KTcpSocket::UnconnectedState); + + s->deleteLater(); +} + +void Server::write() +{ + listener->waitForNewConnection(10000, 0); + socket = listener->nextPendingConnection(); + + socket->waitForReadyRead(40); + socket->write(socket->readAll()); //echo + socket->waitForBytesWritten(150); + + socket->waitForReadyRead(40); + socket->write(socket->readAll()); + cleanupSocket(); +} + +static QString stateToString(KTcpSocket::State state) +{ + switch (state) { + case KTcpSocket::UnconnectedState: + return QStringLiteral("UnconnectedState"); + case KTcpSocket::HostLookupState: + return QStringLiteral("HostLookupState"); + case KTcpSocket::ConnectingState: + return QStringLiteral("ConnectingState"); + case KTcpSocket::ConnectedState: + return QStringLiteral("ConnectedState"); + case KTcpSocket::BoundState: + return QStringLiteral("BoundState"); + case KTcpSocket::ListeningState: + return QStringLiteral("ListeningState"); + case KTcpSocket::ClosingState: + return QStringLiteral("ClosingState"); + } + return QStringLiteral("ERROR"); +} + +#define HTTPREQUEST QByteArray("GET / HTTP/1.1\nHost: www.example.com\n\n") +// I assume that example.com, hosted by the IANA, will exist indefinitely. +// It is a nice test site because it serves a very small HTML page that should +// fit into a TCP packet or two. + +void KTcpSocketTest::statesIana() +{ + QSKIP("Too unreliable"); + //A connection to a real internet host + KTcpSocket *s = new KTcpSocket(this); + connect(s, SIGNAL(hostFound()), this, SLOT(states_hostFound())); + QCOMPARE(s->state(), KTcpSocket::UnconnectedState); + s->connectToHost(QStringLiteral("www.iana.org"), 80); + QCOMPARE(s->state(), KTcpSocket::HostLookupState); + s->write(HTTPREQUEST); + QCOMPARE(s->state(), KTcpSocket::HostLookupState); + s->waitForBytesWritten(2500); + QCOMPARE(s->state(), KTcpSocket::ConnectedState); + + // Try to ensure that inbound data in the next part of the test is really from the second request; + // it is not *guaranteed* that this reads all data, e.g. if the connection is very slow (so too many + // of the waitForReadyRead() time out), or if the reply packets are extremely fragmented (so 50 reads + // are not enough to receive all of them). I don't know the details of fragmentation so the latter + // problem could be nonexistent. + QByteArray received; + for (int i = 0; i < 50; i++) { + s->waitForReadyRead(50); + received.append(s->readAll()); + } + QVERIFY(received.size() > 200); + + // Here, the connection should neither have data in its write buffer nor inbound packets in flight + + // Now reuse the connection for another request / reply pair + + s->write(HTTPREQUEST); + s->waitForReadyRead(); + // After waitForReadyRead(), the write buffer should be empty because the server has to wait for the + // end of the request before sending a reply. + // The socket can then shut down without having to wait for draining the write buffer. + // Incoming data cannot delay the transition to UnconnectedState, as documented in + // QAbstractSocket::disconnectFromHost(). close() just wraps disconnectFromHost(). + s->close(); + QCOMPARE((int)s->state(), (int)KTcpSocket::UnconnectedState); + + delete s; +} + +void KTcpSocketTest::statesLocalHost() +{ + //Now again an internal connection + invokeOnServer("states"); + + KTcpSocket *s = new KTcpSocket(this); + connect(s, SIGNAL(hostFound()), this, SLOT(states_hostFound())); + s->connectToHost(QStringLiteral("127.0.0.1"), testPort); + QCOMPARE(s->state(), KTcpSocket::ConnectingState); + s->waitForConnected(40); + QCOMPARE(s->state(), KTcpSocket::ConnectedState); + + s->write(HTTPREQUEST); + s->waitForReadyRead(); + QCOMPARE((int)s->bytesAvailable(), HTTPREQUEST.size()); //for good measure... + QCOMPARE(s->state(), KTcpSocket::ConnectedState); + + s->waitForDisconnected(40); + QCOMPARE(s->state(), KTcpSocket::UnconnectedState); + + disconnect(s, SIGNAL(hostFound())); + delete s; +} + +void KTcpSocketTest::statesManyHosts() +{ + KTcpSocket *s = new KTcpSocket(this); + QByteArray requestProlog("GET / HTTP/1.1\r\n" //exact copy of a real HTTP query + "Connection: Keep-Alive\r\n" //not really... + "User-Agent: Mozilla/5.0 (compatible; Konqueror/3.96; Linux) " + "KHTML/3.96.0 (like Gecko)\r\n" + "Pragma: no-cache\r\n" + "Cache-control: no-cache\r\n" + "Accept: text/html, image/jpeg, image/png, text/*, image/*, */*\r\n" + "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n" + "Accept-Charset: utf-8, utf-8;q=0.5, *;q=0.5\r\n" + "Accept-Language: en-US, en\r\n" + "Host: "); + QByteArray requestEpilog("\r\n\r\n"); + //Test rapid connection and disconnection to different hosts + static const char *hosts[] = {"www.google.de", "www.spiegel.de", "www.stern.de", "www.laut.de"}; + static const int numHosts = 4; + for (int i = 0; i < numHosts * 5; i++) { + qDebug("\nNow trying %s...", hosts[i % numHosts]); + QCOMPARE(s->state(), KTcpSocket::UnconnectedState); + s->connectToHost(hosts[i % numHosts], 80); + bool skip = false; + KTcpSocket::State expectedState = KTcpSocket::ConnectingState; + + if (i < numHosts) { + expectedState = KTcpSocket::HostLookupState; + } else { + expectedState = KTcpSocket::ConnectingState; + } + + if (!skip) { + QCOMPARE(stateToString(s->state()), stateToString(expectedState)); + } else { // let's make sure it's at least one of the two expected states + QVERIFY(stateToString(s->state()) == stateToString(KTcpSocket::HostLookupState) || + stateToString(s->state()) == stateToString(KTcpSocket::ConnectingState)); + + } + + //weave the host address into the HTTP request + QByteArray request(requestProlog); + request.append(hosts[i % numHosts]); + request.append(requestEpilog); + s->write(request); + + if (!skip) { + QCOMPARE(stateToString(s->state()), stateToString(expectedState)); + } + + s->waitForBytesWritten(-1); + QCOMPARE(s->state(), KTcpSocket::ConnectedState); + int tries = 0; + while (s->bytesAvailable() <= 100 && ++tries < 10) { + s->waitForReadyRead(-1); + } + QVERIFY(s->bytesAvailable() > 100); + if (i % (numHosts + 1)) { + s->readAll(); + QVERIFY(s->bytesAvailable() == 0); + } else { + char dummy[4]; + s->read(dummy, 1); + QVERIFY(s->bytesAvailable() > 100 - 1); + } + s->disconnectFromHost(); + if (s->state() != KTcpSocket::UnconnectedState) { + s->waitForDisconnected(-1); + } + if (i % 2) { + s->close(); //close() is not very well defined for sockets so just check that it + //does no harm + } + } + + s->deleteLater(); +} + +void KTcpSocketTest::states_hostFound() +{ + QCOMPARE(static_cast(sender())->state(), KTcpSocket::ConnectingState); +} + +void Server::states() +{ + listener->waitForNewConnection(10000, 0); + socket = listener->nextPendingConnection(); + + socket->waitForReadyRead(40); + socket->write(socket->readAll()); //echo + socket->waitForBytesWritten(150); + + cleanupSocket(); +} + +void KTcpSocketTest::errors() +{ + //invokeOnServer("errors"); +} + +void Server::errors() +{ + listener->waitForNewConnection(10000, 0); + socket = listener->nextPendingConnection(); + + cleanupSocket(); +} + +QTEST_MAIN(KTcpSocketTest) + +#include "ktcpsockettest.moc" + diff --git a/autotests/ktcpsockettest.h b/autotests/ktcpsockettest.h new file mode 100644 index 0000000..3ff9fd0 --- /dev/null +++ b/autotests/ktcpsockettest.h @@ -0,0 +1,71 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2007 Andreas Hartmetz + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef KTCPSOCKETTEST_H +#define KTCPSOCKETTEST_H + +#include + +class Server; + +class KTcpSocketTest : public QObject +{ + Q_OBJECT +public: + Server *server; + KTcpSocketTest(); + ~KTcpSocketTest(); +private: + void invokeOnServer(const char *); +private Q_SLOTS: + void initTestCase(); + void connectDisconnect(); + void read(); + void write(); + void statesIana(); + void statesLocalHost(); + void statesManyHosts(); + void errors(); +public Q_SLOTS: //auxiliary slots to check signal emission from the socket + void states_hostFound(); +}; + +class QTcpServer; +class QTcpSocket; + +class Server : public QObject +{ + Q_OBJECT +public: + QTcpServer *listener; + QTcpSocket *socket; + quint16 port; + Server(quint16 _port); +private: + void cleanupSocket(); + +public Q_SLOTS: + void connectDisconnect(); + void read(); + void write(); + void states(); + void errors(); +}; + +#endif diff --git a/autotests/kurifiltersearchprovideractionstest.cpp b/autotests/kurifiltersearchprovideractionstest.cpp new file mode 100644 index 0000000..26de292 --- /dev/null +++ b/autotests/kurifiltersearchprovideractionstest.cpp @@ -0,0 +1,84 @@ +/* + Copyright (c) 2015 Montel Laurent + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kurifiltersearchprovideractionstest.h" +#include "kurifiltersearchprovideractions.h" +#include +#include +#include + +void KUriFilterSearchProviderActionsTest::shouldHaveDefaultValue() +{ + KIO::KUriFilterSearchProviderActions shortcutManager; + QVERIFY(shortcutManager.selectedText().isEmpty()); +} + +void KUriFilterSearchProviderActionsTest::shouldAssignSelectedText() +{ + KIO::KUriFilterSearchProviderActions shortcutManager; + const QString selectText = QStringLiteral("foo"); + shortcutManager.setSelectedText(selectText); + QCOMPARE(shortcutManager.selectedText(), selectText); +} + +void KUriFilterSearchProviderActionsTest::shouldAddActionToMenu() +{ + KIO::KUriFilterSearchProviderActions shortcutManager; + QMenu *menu = new QMenu(0); + shortcutManager.addWebShortcutsToMenu(menu); + //Empty when we don't have selected text + QVERIFY(menu->actions().isEmpty()); + + const QString selectText = QStringLiteral("foo"); + + KUriFilterData filterData(selectText); + + filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly); + + QStringList searchProviders; + if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) { + searchProviders = filterData.preferredSearchProviders(); + } + + shortcutManager.setSelectedText(selectText); + shortcutManager.addWebShortcutsToMenu(menu); + QVERIFY(!menu->actions().isEmpty()); + + // Verify that there is a submenu + QVERIFY(menu->actions().at(0)->menu()); + QVERIFY(!menu->actions().at(0)->menu()->actions().isEmpty()); + + QStringList actionData; + Q_FOREACH (const QString &str, searchProviders) { + actionData.append(filterData.queryForPreferredSearchProvider(str)); + } + + int count = 0; + Q_FOREACH (QAction *act, menu->actions().at(0)->menu()->actions()) { + if (!act->data().isNull()) { + QVERIFY(actionData.contains(act->data().toString())); + count++; + } + } + QCOMPARE(count, actionData.count()); + + delete menu; +} + +QTEST_MAIN(KUriFilterSearchProviderActionsTest) diff --git a/autotests/kurifiltersearchprovideractionstest.h b/autotests/kurifiltersearchprovideractionstest.h new file mode 100644 index 0000000..e97e69b --- /dev/null +++ b/autotests/kurifiltersearchprovideractionstest.h @@ -0,0 +1,34 @@ +/* + Copyright (c) 2015 Montel Laurent + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KURIFILTERSEARCHPROVIDERACTIONSTEST_H +#define KURIFILTERSEARCHPROVIDERACTIONSTEST_H + +#include + +class KUriFilterSearchProviderActionsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void shouldHaveDefaultValue(); + void shouldAssignSelectedText(); + void shouldAddActionToMenu(); +}; + +#endif // KURIFILTERSEARCHPROVIDERACTIONSTEST_H diff --git a/autotests/kurifiltertest.cpp b/autotests/kurifiltertest.cpp new file mode 100644 index 0000000..6505395 --- /dev/null +++ b/autotests/kurifiltertest.cpp @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2002, 2003 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kurifiltertest.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +QTEST_MAIN(KUriFilterTest) + +static const char *const s_uritypes[] = { "NetProtocol", "LOCAL_FILE", "LOCAL_DIR", "EXECUTABLE", "HELP", "SHELL", "BLOCKED", "ERROR", "UNKNOWN" }; +#define NO_FILTERING -2 + +static void setupColumns() +{ + QTest::addColumn("input"); + QTest::addColumn("expectedResult"); + QTest::addColumn("expectedUriType"); + QTest::addColumn("list"); + QTest::addColumn("absPath"); + QTest::addColumn("checkForExecutables"); +} + +static void addRow(const char *input, const QString &expectedResult = QString(), int expectedUriType = -1, const QStringList &list = QStringList(), const QString &absPath = QString(), bool checkForExecutables = true) +{ + QTest::newRow(input) << input << expectedResult << expectedUriType << list << absPath << checkForExecutables; +} + +static void runFilterTest(const QString &a, const QString &expectedResult = 0, int expectedUriType = -1, const QStringList &list = QStringList(), const QString &absPath = 0, bool checkForExecutables = true) +{ + KUriFilterData *filterData = new KUriFilterData; + filterData->setData(a); + filterData->setCheckForExecutables(checkForExecutables); + + if (!absPath.isEmpty()) { + filterData->setAbsolutePath(absPath); + qDebug() << "Filtering: " << a << " with absPath=" << absPath; + } else { + qDebug() << "Filtering: " << a; + } + + if (KUriFilter::self()->filterUri(*filterData, list)) { + if (expectedUriType == NO_FILTERING) { + qCritical() << a << "Did not expect filtering. Got" << filterData->uri(); + QVERIFY(expectedUriType != NO_FILTERING); // fail the test + } + + // Copied from minicli... + QString cmd; + QUrl uri = filterData->uri(); + + if (uri.isLocalFile() && !uri.hasFragment() && !uri.hasQuery() && + (filterData->uriType() != KUriFilterData::NetProtocol)) { + cmd = uri.toLocalFile(); + } else { + cmd = uri.url(QUrl::FullyEncoded); + } + + switch (filterData->uriType()) { + case KUriFilterData::LocalFile: + case KUriFilterData::LocalDir: + qDebug() << "*** Result: Local Resource => '" + << filterData->uri().toLocalFile() << "'" << endl; + break; + case KUriFilterData::Help: + qDebug() << "*** Result: Local Resource => '" + << filterData->uri().url() << "'" << endl; + break; + case KUriFilterData::NetProtocol: + qDebug() << "*** Result: Network Resource => '" + << filterData->uri().url() << "'" << endl; + break; + case KUriFilterData::Shell: + case KUriFilterData::Executable: + if (filterData->hasArgsAndOptions()) { + cmd += filterData->argsAndOptions(); + } + qDebug() << "*** Result: Executable/Shell => '" << cmd << "'"; + break; + case KUriFilterData::Error: + qDebug() << "*** Result: Encountered error => '" << cmd << "'"; + qDebug() << "Reason:" << filterData->errorMsg(); + break; + default: + qDebug() << "*** Result: Unknown or invalid resource."; + } + + if (!expectedResult.isEmpty()) { + // Hack for other locales than english, normalize google hosts to google.com + cmd = cmd.replace(QRegExp(QStringLiteral("www\\.google\\.[^/]*/")), QStringLiteral("www.google.com/")); + if (cmd != expectedResult) { + qWarning() << a; + QCOMPARE(cmd, expectedResult); + } + } + + if (expectedUriType != -1 && expectedUriType != filterData->uriType()) { + qWarning() << a << "Got URI type" << s_uritypes[filterData->uriType()] + << "expected" << s_uritypes[expectedUriType]; + QCOMPARE(s_uritypes[filterData->uriType()], + s_uritypes[expectedUriType]); + } + } else { + if (expectedUriType == NO_FILTERING) { + qDebug() << "*** No filtering required."; + } else { + qDebug() << "*** Could not be filtered."; + if (expectedUriType != filterData->uriType()) { + QCOMPARE(s_uritypes[filterData->uriType()], + s_uritypes[expectedUriType]); + } + } + } + + delete filterData; + qDebug() << "-----"; +} + +static void runFilterTest() +{ + QFETCH(QString, input); + QFETCH(QString, expectedResult); + QFETCH(int, expectedUriType); + QFETCH(QStringList, list); + QFETCH(QString, absPath); + QFETCH(bool, checkForExecutables); + runFilterTest(input, expectedResult, expectedUriType, list, absPath, checkForExecutables); +} + +static void testLocalFile(const QString &filename) +{ + QFile tmpFile(filename); // Yeah, I know, security risk blah blah. This is a test prog! + + if (tmpFile.open(QIODevice::ReadWrite)) { + QByteArray fname = QFile::encodeName(tmpFile.fileName()); + runFilterTest(fname, fname, KUriFilterData::LocalFile); + tmpFile.close(); + tmpFile.remove(); + } else { + qDebug() << "Couldn't create " << tmpFile.fileName() << ", skipping test"; + } +} + +static char s_delimiter = ':'; // the alternative is ' ' + +void KUriFilterTest::initTestCase() +{ + QStandardPaths::setTestModeEnabled(true); + minicliFilters << QStringLiteral("kshorturifilter") << QStringLiteral("kurisearchfilter") << QStringLiteral("localdomainurifilter"); + qtdir = qgetenv("QTDIR"); + home = qgetenv("HOME"); + qputenv("DATAHOME", QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation))); + datahome = qgetenv("DATAHOME"); + qDebug() << "libpaths" << QCoreApplication::libraryPaths(); + + qputenv("KDE_FORK_SLAVES", "yes"); // simpler, for the final cleanup + + // Allow testing of the search engine using both delimiters... + const char *envDelimiter = ::getenv("KURIFILTERTEST_DELIMITER"); + if (envDelimiter) { + s_delimiter = envDelimiter[0]; + } + + // Many tests check the "default search engine" feature. + // There is no default search engine by default (since it was annoying when making typos), + // so the user has to set it up, which we do here. + { + KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("kuriikwsfilterrc"), KConfig::SimpleConfig), "General"); + cfg.writeEntry("DefaultWebShortcut", "google"); + cfg.writeEntry("Verbose", true); + cfg.writeEntry("KeywordDelimiter", QString(s_delimiter)); + cfg.sync(); + } + + // Copy kshorturifilterrc from the src dir so we don't depend on make install / env vars. + { + const QString rcFile = QFINDTESTDATA("../src/urifilters/shorturi/kshorturifilterrc"); + QVERIFY(!rcFile.isEmpty()); + const QString localFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/kshorturifilterrc"; + QFile::remove(localFile); + QVERIFY(QFile(rcFile).copy(localFile)); + // Enable verbosity for debugging + KSharedConfig::openConfig(QStringLiteral("kshorturifilterrc"), KConfig::SimpleConfig)->group(QString()).writeEntry("Verbose", true); + } + + QDir().mkpath(datahome + QStringLiteral("/urifilter")); +} + +void KUriFilterTest::noFiltering_data() +{ + setupColumns(); + // URI that should require no filtering + addRow("http://www.kde.org", QStringLiteral("http://www.kde.org"), KUriFilterData::NetProtocol); + addRow("http://www.kde.org/developer//index.html", QStringLiteral("http://www.kde.org/developer//index.html"), KUriFilterData::NetProtocol); + addRow("file:///", QStringLiteral("/"), KUriFilterData::LocalDir); + addRow("file:///etc", QStringLiteral("/etc"), KUriFilterData::LocalDir); + addRow("file:///etc/passwd", QStringLiteral("/etc/passwd"), KUriFilterData::LocalFile); +} + +void KUriFilterTest::noFiltering() +{ + runFilterTest(); +} + +void KUriFilterTest::localFiles_data() +{ + setupColumns(); + addRow("/", QStringLiteral("/"), KUriFilterData::LocalDir); + addRow("/", QStringLiteral("/"), KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); + addRow("//", QStringLiteral("/"), KUriFilterData::LocalDir); + addRow("///", QStringLiteral("/"), KUriFilterData::LocalDir); + addRow("////", QStringLiteral("/"), KUriFilterData::LocalDir); + addRow("///tmp", QStringLiteral("/tmp"), KUriFilterData::LocalDir); + + if (QFile::exists(QDir::homePath() + QLatin1String("/.bashrc"))) { + addRow("~/.bashrc", QDir::homePath() + QStringLiteral("/.bashrc"), KUriFilterData::LocalFile, QStringList(QStringLiteral("kshorturifilter"))); + } + addRow("~", QDir::homePath().toLocal8Bit(), KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter")), QStringLiteral("/tmp")); + addRow("~bin", 0, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); + addRow("~does_not_exist", 0, KUriFilterData::Error, QStringList(QStringLiteral("kshorturifilter"))); + + // Absolute Path tests for kshorturifilter + const QStringList kshorturifilter(QStringLiteral("kshorturifilter")); + addRow("./", datahome, KUriFilterData::LocalDir, kshorturifilter, datahome + QStringLiteral("/")); // cleanPath removes the trailing slash + const QString parentDir = QDir().cleanPath(datahome + QStringLiteral("/..")); + addRow("../", QFile::encodeName(parentDir), KUriFilterData::LocalDir, kshorturifilter, datahome); + addRow("share", datahome, KUriFilterData::LocalDir, kshorturifilter, QFile::encodeName(parentDir)); + // Invalid URLs + addRow("http://a[b]", QStringLiteral("http://a[b]"), KUriFilterData::Unknown, kshorturifilter, QStringLiteral("/")); +} + +void KUriFilterTest::localFiles() +{ + runFilterTest(); +} + +void KUriFilterTest::refOrQuery_data() +{ + setupColumns(); + // URL with reference + addRow("http://www.kde.org/index.html#q8", QStringLiteral("http://www.kde.org/index.html#q8"), KUriFilterData::NetProtocol); + // local file with reference + addRow("file:/etc/passwd#q8", QStringLiteral("file:///etc/passwd#q8"), KUriFilterData::LocalFile); + addRow("file:///etc/passwd#q8", QStringLiteral("file:///etc/passwd#q8"), KUriFilterData::LocalFile); + addRow("/etc/passwd#q8", QStringLiteral("file:///etc/passwd#q8"), KUriFilterData::LocalFile); + // local file with query (can be used by javascript) + addRow("file:/etc/passwd?foo=bar", QStringLiteral("file:///etc/passwd?foo=bar"), KUriFilterData::LocalFile); + testLocalFile(QStringLiteral("/tmp/kurifiltertest?foo")); // local file with ? in the name (#58990) + testLocalFile(QStringLiteral("/tmp/kurlfiltertest#foo")); // local file with '#' in the name + testLocalFile(QStringLiteral("/tmp/kurlfiltertest#foo?bar")); // local file with both + testLocalFile(QStringLiteral("/tmp/kurlfiltertest?foo#bar")); // local file with both, the other way round +} + +void KUriFilterTest::refOrQuery() +{ + runFilterTest(); +} + +void KUriFilterTest::shortUris_data() +{ + setupColumns(); + // hostnames are lowercased by QUrl + addRow("http://www.myDomain.commyPort/ViewObjectRes//Default:name=hello", + QStringLiteral("http://www.mydomain.commyport/ViewObjectRes//Default:name=hello"), KUriFilterData::NetProtocol); + addRow("ftp://ftp.kde.org", QStringLiteral("ftp://ftp.kde.org"), KUriFilterData::NetProtocol); + addRow("ftp://username@ftp.kde.org:500", QStringLiteral("ftp://username@ftp.kde.org:500"), KUriFilterData::NetProtocol); + + // ShortURI/LocalDomain filter tests. + addRow("linuxtoday.com", QStringLiteral("http://linuxtoday.com"), KUriFilterData::NetProtocol); + addRow("LINUXTODAY.COM", QStringLiteral("http://linuxtoday.com"), KUriFilterData::NetProtocol); + addRow("kde.org", QStringLiteral("http://kde.org"), KUriFilterData::NetProtocol); + addRow("ftp.kde.org", QStringLiteral("ftp://ftp.kde.org"), KUriFilterData::NetProtocol); + addRow("ftp.kde.org:21", QStringLiteral("ftp://ftp.kde.org:21"), KUriFilterData::NetProtocol); + addRow("cr.yp.to", QStringLiteral("http://cr.yp.to"), KUriFilterData::NetProtocol); + addRow("www.kde.org:21", QStringLiteral("http://www.kde.org:21"), KUriFilterData::NetProtocol); + // This one passes but the DNS lookup takes 5 seconds to fail + //addRow("foobar.local:8000", QStringLiteral("http://foobar.local:8000"), KUriFilterData::NetProtocol); + addRow("foo@bar.com", QStringLiteral("mailto:foo@bar.com"), KUriFilterData::NetProtocol); + addRow("firstname.lastname@x.foo.bar", QStringLiteral("mailto:firstname.lastname@x.foo.bar"), KUriFilterData::NetProtocol); + addRow("mailto:foo@bar.com", QStringLiteral("mailto:foo@bar.com"), KUriFilterData::NetProtocol); + addRow("www.123.foo", QStringLiteral("http://www.123.foo"), KUriFilterData::NetProtocol); + addRow("user@www.123.foo:3128", QStringLiteral("http://user@www.123.foo:3128"), KUriFilterData::NetProtocol); + addRow("ftp://user@user@www.123.foo:3128", QStringLiteral("ftp://user%40user@www.123.foo:3128"), KUriFilterData::NetProtocol); + addRow("user@user@www.123.foo:3128", QStringLiteral("http://user%40user@www.123.foo:3128"), KUriFilterData::NetProtocol); + + // IPv4 address formats... + addRow("user@192.168.1.0:3128", QStringLiteral("http://user@192.168.1.0:3128"), KUriFilterData::NetProtocol); + addRow("127.0.0.1", QStringLiteral("http://127.0.0.1"), KUriFilterData::NetProtocol); + addRow("127.0.0.1:3128", QStringLiteral("http://127.0.0.1:3128"), KUriFilterData::NetProtocol); + addRow("127.1", QStringLiteral("http://127.0.0.1"), KUriFilterData::NetProtocol); // Qt5: QUrl resolves to 127.0.0.1 + addRow("127.0.1", QStringLiteral("http://127.0.0.1"), KUriFilterData::NetProtocol); // Qt5: QUrl resolves to 127.0.0.1 + + // IPv6 address formats (taken from RFC 2732)... + addRow("[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html", QStringLiteral("http://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:80/index.html"), KUriFilterData::NetProtocol); + addRow("[1080:0:0:0:8:800:200C:417A]/index.html", QStringLiteral("http://[1080::8:800:200c:417a]/index.html"), KUriFilterData::NetProtocol); // Qt5 QUrl change + addRow("[3ffe:2a00:100:7031::1]", QStringLiteral("http://[3ffe:2a00:100:7031::1]"), KUriFilterData::NetProtocol); + addRow("[1080::8:800:200C:417A]/foo", QStringLiteral("http://[1080::8:800:200c:417a]/foo"), KUriFilterData::NetProtocol); + addRow("[::192.9.5.5]/ipng", QStringLiteral("http://[::192.9.5.5]/ipng"), KUriFilterData::NetProtocol); + addRow("[::FFFF:129.144.52.38]:80/index.html", QStringLiteral("http://[::ffff:129.144.52.38]:80/index.html"), KUriFilterData::NetProtocol); + addRow("[2010:836B:4179::836B:4179]", QStringLiteral("http://[2010:836b:4179::836b:4179]"), KUriFilterData::NetProtocol); + + // Local domain filter - If you uncomment these test, make sure you + // you adjust it based on the localhost entry in your /etc/hosts file. + // addRow( "localhost:3128", "http://localhost.localdomain:3128", KUriFilterData::NetProtocol ); + // addRow( "localhost", "http://localhost.localdomain", KUriFilterData::NetProtocol ); + // addRow( "localhost/~blah", "http://localhost.localdomain/~blah", KUriFilterData::NetProtocol ); + + addRow("user@host.domain", QStringLiteral("mailto:user@host.domain"), KUriFilterData::NetProtocol); // new in KDE-3.2 + + // Windows style SMB (UNC) URL. Should be converted into the valid smb format... + addRow("\\\\mainserver\\share\\file", QStringLiteral("smb://mainserver/share/file"), KUriFilterData::NetProtocol); + + // KDE3: was not be filtered at all. All valid protocols of this form were be ignored. + // KDE4: parsed as "network protocol", seems fine to me (DF) + addRow("ftp:", QStringLiteral("ftp:"), KUriFilterData::NetProtocol); + addRow("http:", QStringLiteral("http:"), KUriFilterData::NetProtocol); + + // The default search engine is set to 'Google' + //this may fail if your DNS knows domains KDE or FTP + addRow("gg:", QLatin1String(""), KUriFilterData::NetProtocol); // see bug 56218 + addRow("KDE", QStringLiteral("https://www.google.com/search?q=KDE&ie=UTF-8"), KUriFilterData::NetProtocol); + addRow("HTTP", QStringLiteral("https://www.google.com/search?q=HTTP&ie=UTF-8"), KUriFilterData::NetProtocol); +} + +void KUriFilterTest::shortUris() +{ + runFilterTest(); +} + +void KUriFilterTest::executables_data() +{ + setupColumns(); + // Executable tests - No IKWS in minicli + addRow("cp", QStringLiteral("cp"), KUriFilterData::Executable, minicliFilters); + addRow("kbuildsycoca5", QStringLiteral("kbuildsycoca5"), KUriFilterData::Executable, minicliFilters); + addRow("KDE", QStringLiteral("KDE"), NO_FILTERING, minicliFilters); + addRow("I/dont/exist", QStringLiteral("I/dont/exist"), NO_FILTERING, minicliFilters); //krazy:exclude=spelling + addRow("/I/dont/exist", 0, KUriFilterData::Error, minicliFilters); //krazy:exclude=spelling + addRow("/I/dont/exist#a", 0, KUriFilterData::Error, minicliFilters); //krazy:exclude=spelling + addRow("kbuildsycoca5 --help", QStringLiteral("kbuildsycoca5 --help"), KUriFilterData::Executable, minicliFilters); // the args are in argsAndOptions() + addRow("/bin/sh", QStringLiteral("/bin/sh"), KUriFilterData::Executable, minicliFilters); + addRow("/bin/sh -q -option arg1", QStringLiteral("/bin/sh -q -option arg1"), KUriFilterData::Executable, minicliFilters); // the args are in argsAndOptions() + + // Typing 'cp' or any other valid unix command in konq's location bar should result in + // a search using the default search engine + // 'ls' is a bit of a special case though, due to the toplevel domain called 'ls' + addRow("cp", QStringLiteral("https://www.google.com/search?q=cp&ie=UTF-8"), KUriFilterData::NetProtocol, + QStringList(), 0, false /* don't check for executables, see konq_misc.cc */); +} + +void KUriFilterTest::executables() +{ + runFilterTest(); +} + +void KUriFilterTest::environmentVariables_data() +{ + setupColumns(); + // ENVIRONMENT variable + qputenv("SOMEVAR", "/somevar"); + qputenv("ETC", "/etc"); + + addRow("$SOMEVAR/kdelibs/kio", 0, KUriFilterData::Error); // note: this dir doesn't exist... + addRow("$ETC/passwd", QStringLiteral("/etc/passwd"), KUriFilterData::LocalFile); + QString qtdocPath = qtdir + QStringLiteral("/doc/html/functions.html"); + if (QFile::exists(qtdocPath)) { + QString expectedUrl = QUrl::fromLocalFile(qtdocPath).toString() + "#s"; + addRow("$QTDIR/doc/html/functions.html#s", expectedUrl.toUtf8(), KUriFilterData::LocalFile); + } + addRow("http://www.kde.org/$USER", QStringLiteral("http://www.kde.org/$USER"), KUriFilterData::NetProtocol); // no expansion + + addRow("$DATAHOME", datahome, KUriFilterData::LocalDir); + QDir().mkpath(datahome + QStringLiteral("/urifilter/a+plus")); + addRow("$DATAHOME/urifilter/a+plus", datahome + QStringLiteral("/urifilter/a+plus"), KUriFilterData::LocalDir); + + // BR 27788 + QDir().mkpath(datahome + QStringLiteral("/Dir With Space")); + addRow("$DATAHOME/Dir With Space", datahome + QStringLiteral("/Dir With Space"), KUriFilterData::LocalDir); + + // support for name filters (BR 93825) + addRow("$DATAHOME/*.txt", datahome + QStringLiteral("/*.txt"), KUriFilterData::LocalDir); + addRow("$DATAHOME/[a-b]*.txt", datahome + QStringLiteral("/[a-b]*.txt"), KUriFilterData::LocalDir); + addRow("$DATAHOME/a?c.txt", datahome + QStringLiteral("/a?c.txt"), KUriFilterData::LocalDir); + addRow("$DATAHOME/?c.txt", datahome + QStringLiteral("/?c.txt"), KUriFilterData::LocalDir); + // but let's check that a directory with * in the name still works + QDir().mkpath(datahome + QStringLiteral("/share/Dir*With*Stars")); + addRow("$DATAHOME/Dir*With*Stars", datahome + QStringLiteral("/Dir*With*Stars"), KUriFilterData::LocalDir); + QDir().mkpath(datahome + QStringLiteral("/Dir?QuestionMark")); + addRow("$DATAHOME/Dir?QuestionMark", datahome + QStringLiteral("/Dir?QuestionMark"), KUriFilterData::LocalDir); + QDir().mkpath(datahome + QStringLiteral("/Dir[Bracket")); + addRow("$DATAHOME/Dir[Bracket", datahome + QStringLiteral("/Dir[Bracket"), KUriFilterData::LocalDir); + + addRow("$HOME/$KDEDIR/kdebase/kcontrol/ebrowsing", 0, KUriFilterData::Error); + addRow("$1/$2/$3", QStringLiteral("https://www.google.com/search?q=%241%2F%242%2F%243&ie=UTF-8"), KUriFilterData::NetProtocol); // can be used as bogus or valid test. Currently triggers default search, i.e. google + addRow("$$$$", QStringLiteral("https://www.google.com/search?q=%24%24%24%24&ie=UTF-8"), KUriFilterData::NetProtocol); // worst case scenarios. + + if (!qtdir.isEmpty()) { + addRow("$QTDIR", qtdir, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); //use specific filter. + } + addRow("$HOME", home, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter"))); //use specific filter. +} + +void KUriFilterTest::environmentVariables() +{ + runFilterTest(); +} + +void KUriFilterTest::internetKeywords_data() +{ + setupColumns(); + QString sc; + addRow(sc.sprintf("gg%cfoo bar", s_delimiter).toUtf8(), QStringLiteral("https://www.google.com/search?q=foo+bar&ie=UTF-8"), KUriFilterData::NetProtocol); + addRow(sc.sprintf("bug%c55798", s_delimiter).toUtf8(), QStringLiteral("https://bugs.kde.org/show_bug.cgi?id=55798"), KUriFilterData::NetProtocol); + + addRow(sc.sprintf("gg%cC++", s_delimiter).toUtf8(), QStringLiteral("https://www.google.com/search?q=C%2B%2B&ie=UTF-8"), KUriFilterData::NetProtocol); + addRow(sc.sprintf("gg%cC#", s_delimiter).toUtf8(), QStringLiteral("https://www.google.com/search?q=C%23&ie=UTF-8"), KUriFilterData::NetProtocol); + addRow(sc.sprintf("ya%cfoo bar was here", s_delimiter).toUtf8(), 0, -1); // this triggers default search, i.e. google + addRow(sc.sprintf("gg%cwww.kde.org", s_delimiter).toUtf8(), QStringLiteral("https://www.google.com/search?q=www.kde.org&ie=UTF-8"), KUriFilterData::NetProtocol); + addRow(QStringLiteral("gg%1é").arg(s_delimiter).toUtf8() /*eaccent in utf8*/, QStringLiteral("https://www.google.com/search?q=%C3%A9&ie=UTF-8"), KUriFilterData::NetProtocol); + addRow(QStringLiteral("gg%1прйвет").arg(s_delimiter).toUtf8() /* greetings in russian utf-8*/, QStringLiteral("https://www.google.com/search?q=%D0%BF%D1%80%D0%B9%D0%B2%D0%B5%D1%82&ie=UTF-8"), KUriFilterData::NetProtocol); +} + +void KUriFilterTest::internetKeywords() +{ + runFilterTest(); +} + +void KUriFilterTest::localdomain() +{ + const QString host = QHostInfo::localHostName(); + if (host.isEmpty()) { + const QString expected = QLatin1String("http://") + host; + runFilterTest(host, expected, KUriFilterData::NetProtocol, QStringList() << QStringLiteral("localdomainurifilter"), 0, false); + } +} + diff --git a/autotests/kurifiltertest.h b/autotests/kurifiltertest.h new file mode 100644 index 0000000..ded5cf6 --- /dev/null +++ b/autotests/kurifiltertest.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2006 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KURIFILTERTEST_H +#define KURIFILTERTEST_H + +#include +#include +#include + +class KUriFilterTest : public QObject +{ + Q_OBJECT +public: + +private Q_SLOTS: + void initTestCase(); + void noFiltering_data(); + void noFiltering(); + void localFiles_data(); + void localFiles(); + void shortUris_data(); + void shortUris(); + void refOrQuery_data(); + void refOrQuery(); + void executables_data(); + void executables(); + void environmentVariables_data(); + void environmentVariables(); + void internetKeywords_data(); + void internetKeywords(); + void localdomain(); + +private: + QStringList minicliFilters; + QByteArray qtdir; + QByteArray home; + QByteArray datahome; + +}; + +#endif diff --git a/autotests/kurlcomboboxtest.cpp b/autotests/kurlcomboboxtest.cpp new file mode 100644 index 0000000..270c379 --- /dev/null +++ b/autotests/kurlcomboboxtest.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2015 by Alejandro Fiestas Olivares + +QTEST_MAIN(KUrlComboBoxTest) + +void KUrlComboBoxTest::testTextForItem_data() +{ + QTest::addColumn("url"); + QTest::addColumn("expectedText"); + + QTest::newRow("with_host") << "ftp://foo.com/folder" << "ftp://foo.com/folder/"; + QTest::newRow("with_no_host") << "smb://" << "smb://"; + QTest::newRow("with_host_without_path") << "ftp://user@example.com" << "ftp://user@example.com"; +} + +void KUrlComboBoxTest::testTextForItem() +{ + QFETCH(QString, url); + QFETCH(QString, expectedText); + + KUrlComboBox combo(KUrlComboBox::Directories); + combo.setUrl(QUrl(url)); + + QCOMPARE(combo.itemText(0), expectedText); + +} diff --git a/autotests/kurlcomboboxtest.h b/autotests/kurlcomboboxtest.h new file mode 100644 index 0000000..6f0e9be --- /dev/null +++ b/autotests/kurlcomboboxtest.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2015 by Alejandro Fiestas Olivares + +class KUrlComboBoxTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testTextForItem(); + void testTextForItem_data(); +}; + +#endif //KURLCOMBOBOXTEST_H + diff --git a/autotests/kurlcompletiontest.cpp b/autotests/kurlcompletiontest.cpp new file mode 100644 index 0000000..b611dd1 --- /dev/null +++ b/autotests/kurlcompletiontest.cpp @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2004 David Faure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class KUrlCompletionTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void test(); + +public: + KUrlCompletionTest() { +#ifdef NO_WAIT // kurlcompletiontest-nowait sets this, to test what happens on slower systems (or systems with many dirs or users) + qputenv("KURLCOMPLETION_WAIT", "1"); // 1ms, too short for a full listing of /usr/bin, but at least give a chance for a few items in the result +#endif + } + ~KUrlCompletionTest() + { + teardown(); + } + void runAllTests(); + void setup(); + void teardown(); + void testLocalRelativePath(); + void testLocalAbsolutePath(); + void testLocalURL(); + void testEmptyCwd(); + void testBug346920(); + void testUser(); + void testCancel(); + + // remember to register new test methods in runAllTests + +private: + void waitForCompletion(KUrlCompletion *completion); + KUrlCompletion *m_completion; + QTemporaryDir *m_tempDir; + QUrl m_dirURL; + QString m_dir; + KUrlCompletion *m_completionEmptyCwd; +}; + +void KUrlCompletionTest::setup() +{ + qDebug(); + m_completion = new KUrlCompletion; + m_tempDir = new QTemporaryDir; + m_dir = m_tempDir->path(); + m_dir += QLatin1String("/Dir With#Spaces/"); + QDir().mkdir(m_dir); + qDebug() << "m_dir=" << m_dir; + m_completion->setDir(QUrl::fromLocalFile(m_dir)); + m_dirURL = QUrl::fromLocalFile(m_dir); + + QFile f1(m_dir + "/file1"); + bool ok = f1.open(QIODevice::WriteOnly); + QVERIFY(ok); + f1.close(); + + QFile f2(m_dir + "/file#a"); + ok = f2.open(QIODevice::WriteOnly); + QVERIFY(ok); + f2.close(); + + QFile f3(m_dir + "/file."); + ok = f3.open(QIODevice::WriteOnly); + QVERIFY(ok); + f3.close(); + + QDir().mkdir(m_dir + "/file_subdir"); + QDir().mkdir(m_dir + "/.1_hidden_file_subdir"); + QDir().mkdir(m_dir + "/.2_hidden_file_subdir"); + + m_completionEmptyCwd = new KUrlCompletion; + m_completionEmptyCwd->setDir(QUrl()); +} + +void KUrlCompletionTest::teardown() +{ + delete m_completion; + m_completion = 0; + delete m_tempDir; + m_tempDir = 0; + delete m_completionEmptyCwd; + m_completionEmptyCwd = 0; +} + +void KUrlCompletionTest::waitForCompletion(KUrlCompletion *completion) +{ + while (completion->isRunning()) { + qDebug() << "waiting for thread..."; + QTest::qWait(5); + } + // The thread emitted a signal, process it. + qApp->sendPostedEvents(0, QEvent::MetaCall); +} + +void KUrlCompletionTest::testLocalRelativePath() +{ + qDebug(); + // Completion from relative path, with two matches + m_completion->makeCompletion(QStringLiteral("f")); + waitForCompletion(m_completion); + QStringList comp1all = m_completion->allMatches(); + qDebug() << comp1all; + QCOMPARE(comp1all.count(), 4); + QVERIFY(comp1all.contains("file1")); + QVERIFY(comp1all.contains("file#a")); + QVERIFY(comp1all.contains("file.")); + QVERIFY(comp1all.contains("file_subdir/")); + QString comp1 = m_completion->replacedPath(QStringLiteral("file1")); // like KUrlRequester does + QCOMPARE(comp1, QString("file1")); + + // Completion from relative path + qDebug() << endl << "now completing on 'file#'"; + m_completion->makeCompletion(QStringLiteral("file#")); + QVERIFY(!m_completion->isRunning()); // last listing reused + QStringList compall = m_completion->allMatches(); + qDebug() << compall; + QCOMPARE(compall.count(), 1); + QCOMPARE(compall.first(), QString("file#a")); + QString comp2 = m_completion->replacedPath(compall.first()); // like KUrlRequester does + QCOMPARE(comp2, QString("file#a")); + + // Completion with empty string + qDebug() << endl << "now completing on ''"; + m_completion->makeCompletion(QLatin1String("")); + waitForCompletion(m_completion); + QStringList compEmpty = m_completion->allMatches(); + QCOMPARE(compEmpty.count(), 0); + + // Completion with '.', should find all hidden folders + // This is broken in Qt 5.2 to 5.5 due to aba336c2b4a in qtbase, + // fixed in https://codereview.qt-project.org/143134. +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 1) + m_completion->makeCompletion("."); + waitForCompletion(m_completion); + const auto compAllHidden = m_completion->allMatches(); + QCOMPARE(compAllHidden.count(), 2); + QVERIFY(compAllHidden.contains(".1_hidden_file_subdir/")); + QVERIFY(compAllHidden.contains(".2_hidden_file_subdir/")); +#endif + + // Completion with '.2', should find only hidden folders starting with '2' + m_completion->makeCompletion(".2"); + waitForCompletion(m_completion); + const auto compHiddenStartingWith2 = m_completion->allMatches(); + QCOMPARE(compHiddenStartingWith2.count(), 1); + QVERIFY(compHiddenStartingWith2.contains(".2_hidden_file_subdir/")); + + // Completion with 'file.', should only find one file + m_completion->makeCompletion("file."); + waitForCompletion(m_completion); + const auto compFileEndingWithDot = m_completion->allMatches(); + QCOMPARE(compFileEndingWithDot.count(), 1); + QVERIFY(compFileEndingWithDot.contains("file.")); +} + +void KUrlCompletionTest::testLocalAbsolutePath() +{ + // Completion from absolute path + qDebug() << m_dir + "file#"; + m_completion->makeCompletion(m_dir + "file#"); + waitForCompletion(m_completion); + QStringList compall = m_completion->allMatches(); + qDebug() << compall; + QCOMPARE(compall.count(), 1); + QString comp = compall.first(); + QCOMPARE(comp, QString(m_dir + "file#a")); + comp = m_completion->replacedPath(comp); // like KUrlRequester does + QCOMPARE(comp, QString(m_dir + "file#a")); + + // Completion with '.', should find all hidden folders +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 1) + m_completion->makeCompletion(m_dir + "."); + waitForCompletion(m_completion); + const auto compAllHidden = m_completion->allMatches(); + QCOMPARE(compAllHidden.count(), 2); + QVERIFY(compAllHidden.contains(m_dir + ".1_hidden_file_subdir/")); + QVERIFY(compAllHidden.contains(m_dir + ".2_hidden_file_subdir/")); +#endif + + // Completion with '.2', should find only hidden folders starting with '2' + m_completion->makeCompletion(m_dir + ".2"); + waitForCompletion(m_completion); + const auto compHiddenStartingWith2 = m_completion->allMatches(); + QCOMPARE(compHiddenStartingWith2.count(), 1); + QVERIFY(compHiddenStartingWith2.contains(m_dir + ".2_hidden_file_subdir/")); + + // Completion with 'file.', should only find one file + m_completion->makeCompletion(m_dir + "file."); + waitForCompletion(m_completion); + const auto compFileEndingWithDot = m_completion->allMatches(); + QCOMPARE(compFileEndingWithDot.count(), 1); + QVERIFY(compFileEndingWithDot.contains(m_dir + "file.")); +} + +void KUrlCompletionTest::testLocalURL() +{ + // Completion from URL + qDebug(); + QUrl url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "file"); + m_completion->makeCompletion(url.toString()); + waitForCompletion(m_completion); + QStringList comp1all = m_completion->allMatches(); + qDebug() << comp1all; + QCOMPARE(comp1all.count(), 4); + qDebug() << "Looking for" << m_dirURL.toString() + "file1"; + QVERIFY(comp1all.contains(m_dirURL.toString() + "file1")); + qDebug() << "Looking for" << m_dirURL.toString() + "file."; + QVERIFY(comp1all.contains(m_dirURL.toString() + "file.")); + QVERIFY(comp1all.contains(m_dirURL.toString() + "file_subdir/")); + QString filehash = m_dirURL.toString() + "file%23a"; + qDebug() << "Looking for" << filehash; + QVERIFY(comp1all.contains(filehash)); + QString filehashPath = m_completion->replacedPath(filehash); // note that it returns a path!! + qDebug() << filehashPath; + QCOMPARE(filehashPath, QString(m_dirURL.toLocalFile() + "file#a")); + + // Completion from URL with no match + url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "foobar"); + qDebug() << "makeCompletion(" << url << ")"; + QString comp2 = m_completion->makeCompletion(url.toString()); + QVERIFY(comp2.isEmpty()); + waitForCompletion(m_completion); + QVERIFY(m_completion->allMatches().isEmpty()); + + // Completion from URL with a ref -> no match + url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + 'f'); + url.setFragment(QStringLiteral("ref")); + qDebug() << "makeCompletion(" << url << ")"; + m_completion->makeCompletion(url.toString()); + waitForCompletion(m_completion); + QVERIFY(m_completion->allMatches().isEmpty()); + + // Completion with '.', should find all hidden folders +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 1) + qDebug() << "makeCompletion(" << (m_dirURL.toString() + ".") << ")"; + m_completion->makeCompletion(m_dirURL.toString() + "."); + waitForCompletion(m_completion); + const auto compAllHidden = m_completion->allMatches(); + QCOMPARE(compAllHidden.count(), 2); + QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".1_hidden_file_subdir/")); + QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".2_hidden_file_subdir/")); +#endif + + // Completion with '.2', should find only hidden folders starting with '2' + url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + ".2"); + qDebug() << "makeCompletion(" << url << ")"; + m_completion->makeCompletion(url.toString()); + waitForCompletion(m_completion); + const auto compHiddenStartingWith2 = m_completion->allMatches(); + QCOMPARE(compHiddenStartingWith2.count(), 1); + QVERIFY(compHiddenStartingWith2.contains(m_dirURL.toString() + ".2_hidden_file_subdir/")); + + // Completion with 'file.', should only find one file + url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "file."); + qDebug() << "makeCompletion(" << url << ")"; + m_completion->makeCompletion(url.toString()); + waitForCompletion(m_completion); + const auto compFileEndingWithDot = m_completion->allMatches(); + QCOMPARE(compFileEndingWithDot.count(), 1); + QVERIFY(compFileEndingWithDot.contains(m_dirURL.toString() + "file.")); +} + +void KUrlCompletionTest::testEmptyCwd() +{ + qDebug(); + // Completion with empty string (with a KUrlCompletion whose cwd is "") + qDebug() << endl << "now completing on '' with empty cwd"; + m_completionEmptyCwd->makeCompletion(QLatin1String("")); + waitForCompletion(m_completionEmptyCwd); + QStringList compEmpty = m_completionEmptyCwd->allMatches(); + QCOMPARE(compEmpty.count(), 0); +} + +void KUrlCompletionTest::testBug346920() +{ + m_completionEmptyCwd->makeCompletion(QStringLiteral("~/.")); + waitForCompletion(m_completionEmptyCwd); + m_completionEmptyCwd->allMatches(); + // just don't crash +} + +void KUrlCompletionTest::testUser() +{ + m_completionEmptyCwd->makeCompletion(QStringLiteral("~")); + waitForCompletion(m_completionEmptyCwd); + const auto matches = m_completionEmptyCwd->allMatches(); + if (!KUser::allUserNames().isEmpty()) { + Q_ASSERT(!matches.isEmpty()); + } + foreach (const auto &user, KUser::allUserNames()) { + QVERIFY2(matches.contains(QLatin1Char('~') + user), qPrintable(matches.join(' '))); + } + + // Check that the same query doesn't re-list + m_completionEmptyCwd->makeCompletion(QStringLiteral("~")); + QVERIFY(!m_completionEmptyCwd->isRunning()); + QCOMPARE(m_completionEmptyCwd->allMatches(), matches); +} + +// Test cancelling a running thread +// In a normal run (./kurlcompletiontest) and a reasonable amount of files, we have few chances of making this happen +// But in a "nowait" run (./kurlcompletiontest-nowait), this will cancel the thread before it even starts listing the dir. +void KUrlCompletionTest::testCancel() +{ + KUrlCompletion comp; + comp.setDir(QUrl::fromLocalFile("/usr/bin")); + comp.makeCompletion(QStringLiteral("g")); + const QStringList matchesG = comp.allMatches(); + // We get many matches in a normal run, and usually 0 matches when testing "no wait" (thread is sleeping) -> this is where this method can test cancelling + //qDebug() << "got" << matchesG.count() << "matches"; + bool done = !comp.isRunning(); + + // Doing the same search again, should hopefully not restart everything from scratch + comp.makeCompletion(QStringLiteral("g")); + const QStringList matchesG2 = comp.allMatches(); + QVERIFY(matchesG2.count() >= matchesG.count()); + if (done) { + QVERIFY(!comp.isRunning()); // it had no reason to restart + } + done = !comp.isRunning(); + + // Search for something else, should reuse dir listing but not mix up results + comp.makeCompletion(QStringLiteral("a")); + if (done) { + QVERIFY(!comp.isRunning()); // it had no reason to restart + } + const QStringList matchesA = comp.allMatches(); + //qDebug() << "got" << matchesA.count() << "matches"; + foreach (const QString &match, matchesA) { + QVERIFY2(!match.startsWith('g'), qPrintable(match)); + } + waitForCompletion(&comp); + foreach (const QString &match, comp.allMatches()) { + QVERIFY2(!match.startsWith('g'), qPrintable(match)); + } +} + +void KUrlCompletionTest::test() +{ + runAllTests(); + // Try again, with another QTemporaryDir (to check that the caching doesn't give us wrong results) + runAllTests(); +} + +void KUrlCompletionTest::runAllTests() +{ + setup(); + testLocalRelativePath(); + testLocalAbsolutePath(); + testLocalURL(); + testEmptyCwd(); + testBug346920(); + testUser(); + testCancel(); + teardown(); +} + +QTEST_MAIN(KUrlCompletionTest) + +#include "kurlcompletiontest.moc" diff --git a/autotests/kurlnavigatortest.cpp b/autotests/kurlnavigatortest.cpp new file mode 100644 index 0000000..5f2cc9c --- /dev/null +++ b/autotests/kurlnavigatortest.cpp @@ -0,0 +1,265 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kurlnavigatortest.h" +#include +#include +#include + +#include "kurlnavigator.h" +#include "kurlcombobox.h" + +QTEST_MAIN(KUrlNavigatorTest) + +void KUrlNavigatorTest::initTestCase() +{ + m_navigator = new KUrlNavigator(0, QUrl(QStringLiteral("A")), 0); +} + +void KUrlNavigatorTest::cleanupTestCase() +{ + delete m_navigator; + m_navigator = 0; +} + +void KUrlNavigatorTest::testHistorySizeAndIndex() +{ + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 1); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("A"))); + + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 1); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("B"))); + + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 2); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("C"))); + + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 3); +} + +void KUrlNavigatorTest::testGoBack() +{ + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 3); + + bool ok = m_navigator->goBack(); + + QVERIFY(ok); + QCOMPARE(m_navigator->historyIndex(), 1); + QCOMPARE(m_navigator->historySize(), 3); + + ok = m_navigator->goBack(); + + QVERIFY(ok); + QCOMPARE(m_navigator->historyIndex(), 2); + QCOMPARE(m_navigator->historySize(), 3); + + ok = m_navigator->goBack(); + + QVERIFY(!ok); + QCOMPARE(m_navigator->historyIndex(), 2); + QCOMPARE(m_navigator->historySize(), 3); +} + +void KUrlNavigatorTest::testGoForward() +{ + QCOMPARE(m_navigator->historyIndex(), 2); + QCOMPARE(m_navigator->historySize(), 3); + + bool ok = m_navigator->goForward(); + + QVERIFY(ok); + QCOMPARE(m_navigator->historyIndex(), 1); + QCOMPARE(m_navigator->historySize(), 3); + + ok = m_navigator->goForward(); + + QVERIFY(ok); + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 3); + + ok = m_navigator->goForward(); + + QVERIFY(!ok); + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 3); +} + +void KUrlNavigatorTest::testHistoryInsert() +{ + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 3); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("D"))); + + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 4); + + bool ok = m_navigator->goBack(); + QVERIFY(ok); + QCOMPARE(m_navigator->historyIndex(), 1); + QCOMPARE(m_navigator->historySize(), 4); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("E"))); + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 4); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("F"))); + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 5); + + ok = m_navigator->goBack(); + QVERIFY(ok); + ok = m_navigator->goBack(); + QVERIFY(ok); + QCOMPARE(m_navigator->historyIndex(), 2); + QCOMPARE(m_navigator->historySize(), 5); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("G"))); + + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 4); + + // insert same URL as the current history index + m_navigator->setLocationUrl(QUrl(QStringLiteral("G"))); + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 4); + + // insert same URL with a trailing slash as the current history index + m_navigator->setLocationUrl(QUrl(QStringLiteral("G/"))); + QCOMPARE(m_navigator->historyIndex(), 0); + QCOMPARE(m_navigator->historySize(), 4); + + // jump to "C" and insert same URL as the current history index + ok = m_navigator->goBack(); + QVERIFY(ok); + QCOMPARE(m_navigator->historyIndex(), 1); + QCOMPARE(m_navigator->historySize(), 4); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("C"))); + QCOMPARE(m_navigator->historyIndex(), 1); + QCOMPARE(m_navigator->historySize(), 4); +} + +/** + * When the current URL is inside an archive and the user goes "up", it is expected + * that the new URL is that of the folder containing the archive (unless the URL was + * in a subfolder inside the archive). Furthermore, the protocol should be "file". + * An empty protocol would lead to problems in Dolphin, see + * + * https://bugs.kde.org/show_bug.cgi?id=251553 + */ + +void KUrlNavigatorTest::bug251553_goUpFromArchive() +{ + m_navigator->setLocationUrl(QUrl(QStringLiteral("zip:/test/archive.zip"))); + QCOMPARE(m_navigator->locationUrl().path(), QLatin1String("/test/archive.zip")); + QCOMPARE(m_navigator->locationUrl().scheme(), QLatin1String("zip")); + + bool ok = m_navigator->goUp(); + QVERIFY(ok); + QCOMPARE(m_navigator->locationUrl().path(), QLatin1String("/test/")); + QCOMPARE(m_navigator->locationUrl().scheme(), QLatin1String("file")); + + m_navigator->setLocationUrl(QUrl(QStringLiteral("tar:/test/archive.tar.gz"))); + QCOMPARE(m_navigator->locationUrl().path(), QLatin1String("/test/archive.tar.gz")); + QCOMPARE(m_navigator->locationUrl().scheme(), QLatin1String("tar")); + + ok = m_navigator->goUp(); + QVERIFY(ok); + QCOMPARE(m_navigator->locationUrl().path(), QLatin1String("/test/")); + QCOMPARE(m_navigator->locationUrl().scheme(), QLatin1String("file")); +} + +void KUrlNavigatorTest::testUrlParsing_data() +{ + QTest::addColumn("input"); + QTest::addColumn("url"); + // due to a bug in the KF5 porting input such as '/home/foo/.config' was parsed as 'http:///home/foo/.config/'. + QTest::newRow("hiddenFile") << QStringLiteral("/home/foo/.config") << QUrl::fromLocalFile(QStringLiteral("/home/foo/.config")); + // TODO: test this on windows: e.g. 'C:/foo/.config' or 'C:\foo\.config' + QTest::newRow("homeDir") << QStringLiteral("~") << QUrl::fromLocalFile(QDir::homePath()); + KUser user(KUser::UseRealUserID); + QTest::newRow("userHomeDir") << (QStringLiteral("~") + user.loginName()) << QUrl::fromLocalFile(user.homeDir()); +} + +void KUrlNavigatorTest::testUrlParsing() +{ + QFETCH(QString, input); + QFETCH(QUrl, url); + + m_navigator->setLocationUrl(QUrl()); + m_navigator->setUrlEditable(true); + m_navigator->editor()->setCurrentText(input); + QCOMPARE(m_navigator->uncommittedUrl(), url); + QTest::keyClick(m_navigator->editor(), Qt::Key_Enter); + QCOMPARE(m_navigator->locationUrl(), url); +} + +void KUrlNavigatorTest::testButtonUrl_data() +{ + QTest::addColumn("locationUrl"); + QTest::addColumn("buttonIndex"); + QTest::addColumn("expectedButtonUrl"); + + QTest::newRow("localPathButtonIndex3") << QUrl::fromLocalFile(QStringLiteral("/home/foo")) << 3 << QUrl::fromLocalFile(QStringLiteral("/home/foo")); // out of range + QTest::newRow("localPathButtonIndex2") << QUrl::fromLocalFile(QStringLiteral("/home/foo")) << 2 << QUrl::fromLocalFile(QStringLiteral("/home/foo")); + QTest::newRow("localPathButtonIndex1") << QUrl::fromLocalFile(QStringLiteral("/home/foo")) << 1 << QUrl::fromLocalFile(QStringLiteral("/home")); + QTest::newRow("localPathButtonIndex0") << QUrl::fromLocalFile(QStringLiteral("/home/foo")) << 0 << QUrl::fromLocalFile(QStringLiteral("/")); + + QTest::newRow("networkPathButtonIndex1") << QUrl::fromUserInput(QStringLiteral("network:/konqi.local/share")) << 1 << QUrl::fromUserInput(QStringLiteral("network:/konqi.local")); + QTest::newRow("networkPathButtonIndex0") << QUrl::fromUserInput(QStringLiteral("network:/konqi.local/share")) << 0 << QUrl::fromUserInput(QStringLiteral("network:/")); + + QTest::newRow("ftpPathButtonIndex1") << QUrl::fromUserInput(QStringLiteral("ftp://kde.org/home/foo")) << 1 << QUrl::fromUserInput(QStringLiteral("ftp://kde.org/home")); + QTest::newRow("ftpPathButtonIndex0") << QUrl::fromUserInput(QStringLiteral("ftp://kde.org/home/foo")) << 0 << QUrl::fromUserInput(QStringLiteral("ftp://kde.org/")); + + // bug 354678 + QTest::newRow("localPathWithPercentage") << QUrl::fromLocalFile(QStringLiteral("/home/foo %/test")) << 2 << QUrl::fromLocalFile(QStringLiteral("/home/foo %")); +} + +void KUrlNavigatorTest::testButtonUrl() +{ + QFETCH(QUrl, locationUrl); + QFETCH(int, buttonIndex); + QFETCH(QUrl, expectedButtonUrl); + + // PREPARE + m_navigator->setLocationUrl(locationUrl); + + // WHEN + const QUrl buttonUrl = m_navigator->url(buttonIndex); + + // THEN + QCOMPARE(buttonUrl, expectedButtonUrl); +} + +void KUrlNavigatorTest::testInitWithRedundantPathSeparators() +{ + KUrlNavigator temp_nav(0, QUrl::fromLocalFile(QStringLiteral("/home/foo///test")), 0); + + const QUrl buttonUrl = temp_nav.url(3); + + QCOMPARE(buttonUrl, QUrl::fromLocalFile(QStringLiteral("/home/foo/test"))); +} diff --git a/autotests/kurlnavigatortest.h b/autotests/kurlnavigatortest.h new file mode 100644 index 0000000..3f3584d --- /dev/null +++ b/autotests/kurlnavigatortest.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef URLNAVIGATORTEST_H +#define URLNAVIGATORTEST_H + +#include +class KUrlNavigator; + +class KUrlNavigatorTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void testHistorySizeAndIndex(); + void testGoBack(); + void testGoForward(); + void testHistoryInsert(); + + void bug251553_goUpFromArchive(); + + void testUrlParsing_data(); + void testUrlParsing(); + + void testButtonUrl_data(); + void testButtonUrl(); + + void testInitWithRedundantPathSeparators(); + + +private: + KUrlNavigator *m_navigator; +}; + +#endif diff --git a/autotests/kurlrequestertest.cpp b/autotests/kurlrequestertest.cpp new file mode 100644 index 0000000..af2a5ec --- /dev/null +++ b/autotests/kurlrequestertest.cpp @@ -0,0 +1,142 @@ +/* This file is part of the KDE Frameworks + Copyright (c) 2008, 2016 David Faure + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +/* +IMPORTANT: + Because this unittest interacts with the file dialog, + remember to run it both with plugins/platformthemes/KDEPlasmaPlatformTheme.so (to use KFileWidget) + and without it (to use the builtin QFileDialog code) +*/ + +class KUrlRequesterTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testUrlRequester(); + void testComboRequester(); + +private: + bool createTestFile(const QString &fileName) { + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + file.write("Hello world\n"); + return true; + } +}; + +// Same as in kfiledialog_unittest.cpp +static KFileWidget *findFileWidget() +{ + QList widgets; + foreach (QWidget *widget, QApplication::topLevelWidgets()) { + KFileWidget *fw = widget->findChild(); + if (fw) { + widgets.append(fw); + } + } + return (widgets.count() == 1) ? widgets.first() : Q_NULLPTR; +} + + +void KUrlRequesterTest::testUrlRequester() +{ + KUrlRequester req; + req.setFileDialogModality(Qt::NonModal); + const QString fileName = QStringLiteral("some_test_file"); + QVERIFY(createTestFile(fileName)); + QTemporaryFile tempFile; + QVERIFY(tempFile.open()); + const QString filePath2 = tempFile.fileName(); + QVERIFY(QFile::exists(filePath2)); + + // Set start dir + const QUrl dirUrl = QUrl::fromLocalFile(QDir::currentPath()); + req.setStartDir(dirUrl); + QCOMPARE(req.startDir().toString(), dirUrl.toString()); + + // Click the button + req.button()->click(); + QFileDialog *fileDialog = req.findChild(); + QVERIFY(fileDialog); + + // Find out if we're using KFileDialog or QFileDialog + KFileWidget *fw = findFileWidget(); + + // Wait for directory listing + if (fw) { + QSignalSpy spy(fw->dirOperator(), &KDirOperator::finishedLoading); + QVERIFY(spy.wait()); + } + + // Select file + const QString filePath = dirUrl.toLocalFile() + '/' + fileName; + fileDialog->selectFile(fileName); + + // Click OK, check URLRequester shows and returns selected file + QKeyEvent keyPressEv(QKeyEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); + qApp->sendEvent(fw ? static_cast(fw) : static_cast(fileDialog), &keyPressEv); + QCOMPARE(fileDialog->result(), static_cast(QDialog::Accepted)); + QCOMPARE(fileDialog->selectedFiles(), QStringList() << filePath); + QCOMPARE(req.url().toLocalFile(), filePath); + + // Check there is no longer any file dialog visible + QVERIFY(fileDialog->isHidden()); + + // Click KUrlRequester button again. This time the filedialog is initialized with a file URL + req.button()->click(); + fileDialog = req.findChild(); + QVERIFY(fileDialog); + fw = findFileWidget(); + if (fw) { // no need to wait for dir listing again, but we need it to be visible at least (for Key_Return to accept) + //QVERIFY(QTest::qWaitForWindowExposed(fw->window())); // doesn't seem to be enough + QTRY_VERIFY(fw->isVisible()); + } + + // Select file 2 + fileDialog->selectFile(filePath2); + + // Click OK, check URLRequester shows and returns selected file + qApp->sendEvent(fw ? static_cast(fw) : static_cast(fileDialog), &keyPressEv); + QCOMPARE(fileDialog->result(), static_cast(QDialog::Accepted)); + QCOMPARE(fileDialog->selectedFiles(), QStringList() << filePath2); + QCOMPARE(req.url().toLocalFile(), filePath2); +} + +void KUrlRequesterTest::testComboRequester() +{ + KUrlComboRequester req; + QList lineEdits = req.findChildren(); + QVERIFY(lineEdits.isEmpty()); // no lineedits, only a readonly combo +} + +QTEST_MAIN(KUrlRequesterTest) +#include "kurlrequestertest.moc" diff --git a/autotests/listdirtest.cpp b/autotests/listdirtest.cpp new file mode 100644 index 0000000..38ad9bd --- /dev/null +++ b/autotests/listdirtest.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2013 Mark Gaiser + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "listdirtest.h" + +#include +#include +#include +#include +#include +#include + +QTEST_MAIN(ListDirTest) + +void ListDirTest::initTestCase() +{ + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); +} + +void ListDirTest::numFilesTestCase_data() +{ + QTest::addColumn("numOfFiles"); + QTest::newRow("10 files") << 10; + QTest::newRow("100 files") << 100; + QTest::newRow("1000 files") << 1000; +} + +void ListDirTest::numFilesTestCase() +{ + QFETCH(int, numOfFiles); + + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + createEmptyTestFiles(numOfFiles, tempDir.path()); + + /*QBENCHMARK*/ { + m_receivedEntryCount = -2; // We start at -2 for . and .. slotResult will just increment this value + KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(tempDir.path()), KIO::HideProgressInfo); + job->setUiDelegate(0); + connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); + + QSignalSpy spy(job, SIGNAL(result(KJob*))); + QVERIFY(spy.wait(100000)); + QCOMPARE(job->error(), 0); // no error + } + QCOMPARE(m_receivedEntryCount, numOfFiles); +} + +void ListDirTest::slotEntries(KIO::Job *, const KIO::UDSEntryList &entries) +{ + m_receivedEntryCount += entries.count(); +} + +void ListDirTest::createEmptyTestFiles(int numOfFilesToCreate, const QString &path) +{ + for (int i = 0; i < numOfFilesToCreate; i++) { + const QString filename = path + QDir::separator() + QString::number(i) + ".txt"; + QFile file(filename); + QVERIFY(file.open(QIODevice::WriteOnly)); + } + + QCOMPARE(QDir(path).entryList(QDir::Files).count(), numOfFilesToCreate); +} diff --git a/autotests/listdirtest.h b/autotests/listdirtest.h new file mode 100644 index 0000000..92dc32f --- /dev/null +++ b/autotests/listdirtest.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2013 Mark Gaiser + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef LISTDIRTEST_H +#define LISTDIRTEST_H + +#include +#include + +class ListDirTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void numFilesTestCase_data(); + void numFilesTestCase(); + + void slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries); + +private: + void createEmptyTestFiles(int numOfFilesToCreate, const QString &path); + int m_receivedEntryCount; +}; + +#endif diff --git a/autotests/mkpathjobtest.cpp b/autotests/mkpathjobtest.cpp new file mode 100644 index 0000000..e3a9c0a --- /dev/null +++ b/autotests/mkpathjobtest.cpp @@ -0,0 +1,148 @@ +/* This file is part of the KDE project + Copyright (C) 2014 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include + +#include + +class MkpathJobTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase() + { + QStandardPaths::enableTestMode(true); + + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + + QVERIFY(m_tempDir.isValid()); + m_dir = m_tempDir.path(); + } + + void cleanupTestCase() + { + } + + void shouldDoNothingIfExists() + { + QVERIFY(QFile::exists(m_dir)); + const QStringList oldEntries = QDir(m_dir).entryList(); + KIO::Job *job = KIO::mkpath(QUrl::fromLocalFile(m_dir)); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(directoryCreated(QUrl))); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QVERIFY(QFile::exists(m_dir)); + QCOMPARE(spy.count(), 0); + QCOMPARE(QDir(m_dir).entryList(), oldEntries); // nothing got created in there + } + + void shouldCreateOneDirectory() + { + QUrl url = QUrl::fromLocalFile(m_dir); + url.setPath(url.path() + "/subdir1"); + KIO::Job *job = KIO::mkpath(url); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(directoryCreated(QUrl))); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(spy.count(), 1); + QVERIFY(QFile::exists(url.toLocalFile())); + } + + void shouldCreateTwoDirectories() + { + QUrl url = QUrl::fromLocalFile(m_dir); + url.setPath(url.path() + "/subdir2/subsubdir"); + KIO::Job *job = KIO::mkpath(url); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(directoryCreated(QUrl))); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(spy.count(), 2); + QVERIFY(QFile::exists(url.toLocalFile())); + } + + void shouldDoNothingIfExistsWithBasePath() + { + const QStringList oldEntries = QDir(m_dir).entryList(); + QUrl url = QUrl::fromLocalFile(m_dir); + KIO::Job *job = KIO::mkpath(url, url); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(directoryCreated(QUrl))); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(job->totalAmount(KJob::Directories), 0ULL); + QCOMPARE(spy.count(), 0); + QVERIFY(QFile::exists(url.toLocalFile())); + QCOMPARE(QDir(m_dir).entryList(), oldEntries); // nothing got created in there + } + + void shouldCreateOneDirectoryWithBasePath() + { + QUrl url = QUrl::fromLocalFile(m_dir); + const QUrl baseUrl = url; + url.setPath(url.path() + "/subdir3"); + KIO::Job *job = KIO::mkpath(url, baseUrl); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(directoryCreated(QUrl))); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(spy.count(), 1); + QCOMPARE(job->totalAmount(KJob::Directories), 1ULL); + QVERIFY(QFile::exists(url.toLocalFile())); + } + + void shouldCreateTwoDirectoriesWithBasePath() + { + QUrl url = QUrl::fromLocalFile(m_dir); + const QUrl baseUrl = url; + url.setPath(url.path() + "/subdir4/subsubdir"); + KIO::Job *job = KIO::mkpath(url, baseUrl); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(directoryCreated(QUrl))); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(spy.count(), 2); + QCOMPARE(job->totalAmount(KJob::Directories), 2ULL); + QVERIFY(QFile::exists(url.toLocalFile())); + } + + void shouldIgnoreUnrelatedBasePath() + { + QUrl url = QUrl::fromLocalFile(m_dir); + const QUrl baseUrl = url; + url.setPath(url.path() + "/subdir5/subsubdir"); + KIO::Job *job = KIO::mkpath(url, QUrl::fromLocalFile(QStringLiteral("/does/not/exist"))); + job->setUiDelegate(0); + QSignalSpy spy(job, SIGNAL(directoryCreated(QUrl))); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QCOMPARE(spy.count(), 2); + QVERIFY(QFile::exists(url.toLocalFile())); + } + +private: + QTemporaryDir m_tempDir; + QString m_dir; +}; + +QTEST_MAIN(MkpathJobTest) + +#include "mkpathjobtest.moc" + diff --git a/autotests/pastetest.cpp b/autotests/pastetest.cpp new file mode 100644 index 0000000..28d1238 --- /dev/null +++ b/autotests/pastetest.cpp @@ -0,0 +1,228 @@ +/* This file is part of KDE + Copyright (c) 2005-2006 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "pastetest.h" + +QTEST_MAIN(KIOPasteTest) + +void KIOPasteTest::initTestCase() +{ + QStandardPaths::enableTestMode(true); + + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + + QVERIFY(m_tempDir.isValid()); + m_dir = m_tempDir.path(); +} + +void KIOPasteTest::testPopulate() +{ + QMimeData *mimeData = new QMimeData; + + // Those URLs don't have to exist. + QUrl mediaURL(QStringLiteral("media:/hda1/tmp/Mat%C3%A9riel")); + QUrl localURL(QStringLiteral("file:///tmp/Mat%C3%A9riel")); + QList kdeURLs; kdeURLs << mediaURL; + QList mostLocalURLs; mostLocalURLs << localURL; + + KUrlMimeData::setUrls(kdeURLs, mostLocalURLs, mimeData); + + QVERIFY(mimeData->hasUrls()); + const QList lst = KUrlMimeData::urlsFromMimeData(mimeData); + QCOMPARE(lst.count(), 1); + QCOMPARE(lst[0].url(), mediaURL.url()); + + const bool isCut = KIO::isClipboardDataCut(mimeData); + QVERIFY(!isCut); + + delete mimeData; +} + +void KIOPasteTest::testCut() +{ + QMimeData *mimeData = new QMimeData; + + QUrl localURL1(QStringLiteral("file:///tmp/Mat%C3%A9riel")); + QUrl localURL2(QStringLiteral("file:///tmp")); + QList localURLs; localURLs << localURL1 << localURL2; + + KUrlMimeData::setUrls(QList(), localURLs, mimeData); + KIO::setClipboardDataCut(mimeData, true); + + QVERIFY(mimeData->hasUrls()); + const QList lst = KUrlMimeData::urlsFromMimeData(mimeData); + QCOMPARE(lst.count(), 2); + QCOMPARE(lst[0].url(), localURL1.url()); + QCOMPARE(lst[1].url(), localURL2.url()); + + const bool isCut = KIO::isClipboardDataCut(mimeData); + QVERIFY(isCut); + + delete mimeData; +} + +void KIOPasteTest::testPasteActionText_data() +{ + QTest::addColumn >("urls"); + QTest::addColumn("data"); + QTest::addColumn("expectedEnabled"); + QTest::addColumn("expectedText"); + + QList urlDir = QList() << QUrl::fromLocalFile(QDir::tempPath()); + QList urlFile = QList() << QUrl::fromLocalFile(QCoreApplication::applicationFilePath()); + QList urlRemote = QList() << QUrl(QStringLiteral("http://www.kde.org")); + QList urls = urlDir + urlRemote; + QTest::newRow("nothing") << QList() << false << false << "Paste"; + QTest::newRow("one_dir") << urlDir << false << true << "Paste One Folder"; + QTest::newRow("one_file") << urlFile << false << true << "Paste One File"; + QTest::newRow("one_url") << urlRemote << false << true << "Paste One Item"; + QTest::newRow("two_urls") << urls << false << true << "Paste 2 Items"; + QTest::newRow("data") << QList() << true << true << "Paste Clipboard Contents..."; +} + +void KIOPasteTest::testPasteActionText() +{ + QFETCH(QList, urls); + QFETCH(bool, data); + QFETCH(bool, expectedEnabled); + QFETCH(QString, expectedText); + + QMimeData mimeData; + if (!urls.isEmpty()) { + mimeData.setUrls(urls); + } + if (data) { + mimeData.setText(QStringLiteral("foo")); + } + QCOMPARE(KIO::canPasteMimeData(&mimeData), expectedEnabled); + bool canPaste; + KFileItem destItem(QUrl::fromLocalFile(QDir::homePath())); + QCOMPARE(KIO::pasteActionText(&mimeData, &canPaste, destItem), expectedText); + QCOMPARE(canPaste, expectedEnabled); + + KFileItem nonWritableDestItem(QUrl::fromLocalFile(QStringLiteral("/nonwritable"))); + QCOMPARE(KIO::pasteActionText(&mimeData, &canPaste, nonWritableDestItem), expectedText); + QCOMPARE(canPaste, false); + + KFileItem emptyUrlDestItem = KFileItem(QUrl()); + QCOMPARE(KIO::pasteActionText(&mimeData, &canPaste, emptyUrlDestItem), expectedText); + QCOMPARE(canPaste, false); + + KFileItem nullDestItem; + QCOMPARE(KIO::pasteActionText(&mimeData, &canPaste, nullDestItem), expectedText); + QCOMPARE(canPaste, false); +} + +static void createTestFile(const QString &path) +{ + QFile f(path); + if (!f.open(QIODevice::WriteOnly)) { + qFatal("Couldn't create %s", qPrintable(path)); + } + QByteArray data("Hello world", 11); + QCOMPARE(data.size(), 11); + f.write(data); +} + +void KIOPasteTest::testPasteJob_data() +{ + QTest::addColumn >("urls"); + QTest::addColumn("data"); + QTest::addColumn("cut"); + QTest::addColumn("expectedFileName"); + + const QString file = m_dir + "/file"; + createTestFile(file); + + QList urlFile = QList() << QUrl::fromLocalFile(file); + QList urlDir = QList() << QUrl::fromLocalFile(m_dir); + + QTest::newRow("nothing") << QList() << false << false << QString(); + QTest::newRow("copy_one_file") << urlFile << false << false << file.section('/', -1); + QTest::newRow("copy_one_dir") << urlDir << false << false << m_dir.section('/', -1); + QTest::newRow("cut_one_file") << urlFile << false << true << file.section('/', -1); + QTest::newRow("cut_one_dir") << urlDir << false << true << m_dir.section('/', -1); + + // Shows a dialog! + //QTest::newRow("data") << QList() << true << "output_file"; +} + +void KIOPasteTest::testPasteJob() +{ + QFETCH(QList, urls); + QFETCH(bool, data); + QFETCH(bool, cut); + QFETCH(QString, expectedFileName); + + QMimeData mimeData; + bool isDir = false; + bool isFile = false; + if (!urls.isEmpty()) { + mimeData.setUrls(urls); + QFileInfo fileInfo(urls.first().toLocalFile()); + isDir = fileInfo.isDir(); + isFile = fileInfo.isFile(); + } + if (data) { + mimeData.setText(QStringLiteral("Hello world")); + } + KIO::setClipboardDataCut(&mimeData, cut); + + QTemporaryDir destTempDir; + QVERIFY(destTempDir.isValid()); + const QString destDir = destTempDir.path(); + KIO::Job *job = KIO::paste(&mimeData, QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); + QSignalSpy spy(job, SIGNAL(itemCreated(QUrl))); + QVERIFY(spy.isValid()); + job->setUiDelegate(0); + const bool expectedSuccess = !expectedFileName.isEmpty(); + QCOMPARE(job->exec(), expectedSuccess); + if (expectedSuccess) { + const QString destFile = destDir + '/' + expectedFileName; + QVERIFY2(QFile::exists(destFile), qPrintable(expectedFileName)); + if (isDir) { + QVERIFY(QFileInfo(destFile).isDir()); + } else { + QVERIFY(QFileInfo(destFile).isFile()); + QFile file(destFile); + QVERIFY(file.open(QIODevice::ReadOnly)); + QCOMPARE(QString(file.readAll()), QString("Hello world")); + } + if (cut) { + QVERIFY(!QFile::exists(urls.first().toLocalFile())); + } else { + QVERIFY(QFile::exists(urls.first().toLocalFile())); + } + QCOMPARE(spy.count(), isFile || cut ? 1 : 2); + QCOMPARE(spy.at(0).at(0).value().toLocalFile(), destFile); + } +} diff --git a/autotests/pastetest.h b/autotests/pastetest.h new file mode 100644 index 0000000..040e6ee --- /dev/null +++ b/autotests/pastetest.h @@ -0,0 +1,45 @@ +/* This file is part of KDE Frameworks + Copyright (c) 2005-2006 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KIOPASTETEST_H +#define KIOPASTETEST_H + +#include +#include +#include + +class KIOPasteTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + + void testPopulate(); + void testCut(); + void testPasteActionText_data(); + void testPasteActionText(); + void testPasteJob_data(); + void testPasteJob(); + +private: + QTemporaryDir m_tempDir; + QString m_dir; +}; + +#endif diff --git a/autotests/threadtest.cpp b/autotests/threadtest.cpp new file mode 100644 index 0000000..0563bbc --- /dev/null +++ b/autotests/threadtest.cpp @@ -0,0 +1,107 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2014 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include + +#include +#include "kiotesthelper.h" // homeTmpDir, createTestFile etc. + +class KIOThreadTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void concurrentCopying(); + void cleanupTestCase(); + +private: + struct FileData; + bool copyLocalFile(FileData *fileData); +}; + +void KIOThreadTest::initTestCase() +{ + QStandardPaths::enableTestMode(true); + + // To avoid a runtime dependency on klauncher + qputenv("KDE_FORK_SLAVES", "yes"); + // Start with a clean base dir + cleanupTestCase(); + homeTmpDir(); // create it + + QCOMPARE(sizeof(int), sizeof(QAtomicInt)); + + Q_UNUSED(createTestDirectory); +} + +void KIOThreadTest::cleanupTestCase() +{ + QDir(homeTmpDir()).removeRecursively(); +} + +struct KIOThreadTest::FileData { + QString src; + QString dest; +}; + +bool KIOThreadTest::copyLocalFile(FileData *fileData) +{ + // to verify the test harness: return QFile::copy(fileData->src, fileData->dest); + + const QUrl u = QUrl::fromLocalFile(fileData->src); + const QUrl d = QUrl::fromLocalFile(fileData->dest); + + // copy the file with file_copy + KIO::Job *job = KIO::file_copy(u, d, -1, KIO::HideProgressInfo); + //qDebug() << job << u << d; + job->setUiDelegate(0); + bool ret = job->exec(); + //qDebug() << job << "done"; + return ret; +} + +void KIOThreadTest::concurrentCopying() +{ + const int numThreads = 20; + QVector data(numThreads); + for (int i = 0; i < numThreads; ++i) { + data[i].src = homeTmpDir() + "file" + QString::number(i); + data[i].dest = homeTmpDir() + "file" + QString::number(i) + "_copied"; + createTestFile(data[i].src); + } + QThreadPool tp; + tp.setMaxThreadCount(numThreads); + QVector> futures(numThreads); + for (int i = 0; i < numThreads; ++i) { + futures[i] = QtConcurrent::run(&tp, this, &KIOThreadTest::copyLocalFile, &data[i]); + } + QVERIFY(tp.waitForDone(60000)); + + for (int i = 0; i < numThreads; ++i) { + QVERIFY(QFile::exists(data[i].dest)); + QVERIFY(futures.at(i).result()); + } +} + +QTEST_MAIN(KIOThreadTest) +#include "threadtest.moc" + diff --git a/autotests/udsentry_benchmark.cpp b/autotests/udsentry_benchmark.cpp new file mode 100644 index 0000000..a3384ff --- /dev/null +++ b/autotests/udsentry_benchmark.cpp @@ -0,0 +1,444 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2014 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include +#include + +#include +#include // filesize_t + +/* + This is to compare the old list-of-lists API vs a QMap/QHash-based API + in terms of performance. + + The number of atoms and their type map to what kio_file would put in + for any normal file. + + The lookups are done for two atoms that are present, and for one that is not. +*/ + +class UdsEntryBenchmark : public QObject +{ + Q_OBJECT +public: + UdsEntryBenchmark() + : nameStr(QStringLiteral("name")), + now(QDateTime::currentDateTime()), + now_time_t(now.toTime_t()) + {} +private Q_SLOTS: + void testKDE3Slave(); + void testKDE3App(); + void testHashVariantSlave(); + void testHashVariantApp(); + void testHashStructSlave(); + void testHashStructApp(); + void testMapStructSlave(); + void testMapStructApp(); + void testTwoVectorsSlave(); + void testTwoVectorsApp(); +private: + const QString nameStr; + const QDateTime now; + const time_t now_time_t; +}; + +class OldUDSAtom +{ +public: + QString m_str; + long long m_long; + unsigned int m_uds; +}; +typedef QList OldUDSEntry; // well it was a QValueList :) + +static void fillOldUDSEntry(OldUDSEntry &entry, time_t now_time_t, const QString &nameStr) +{ + OldUDSAtom atom; + atom.m_uds = KIO::UDSEntry::UDS_NAME; + atom.m_str = nameStr; + entry.append(atom); + atom.m_uds = KIO::UDSEntry::UDS_SIZE; + atom.m_long = 123456ULL; + entry.append(atom); + atom.m_uds = KIO::UDSEntry::UDS_MODIFICATION_TIME; + atom.m_long = now_time_t; + entry.append(atom); + atom.m_uds = KIO::UDSEntry::UDS_ACCESS_TIME; + atom.m_long = now_time_t; + entry.append(atom); + atom.m_uds = KIO::UDSEntry::UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + atom.m_uds = KIO::UDSEntry::UDS_ACCESS; + atom.m_long = 0644; + entry.append(atom); + atom.m_uds = KIO::UDSEntry::UDS_USER; + atom.m_str = nameStr; + entry.append(atom); + atom.m_uds = KIO::UDSEntry::UDS_GROUP; + atom.m_str = nameStr; + entry.append(atom); +} + +void UdsEntryBenchmark::testKDE3Slave() +{ + QBENCHMARK { + OldUDSEntry entry; + fillOldUDSEntry(entry, now_time_t, nameStr); + QCOMPARE(entry.count(), 8); + } +} + +void UdsEntryBenchmark::testKDE3App() +{ + OldUDSEntry entry; + fillOldUDSEntry(entry, now_time_t, nameStr); + + QString displayName; + KIO::filesize_t size; + QString url; + + QBENCHMARK { + OldUDSEntry::ConstIterator it2 = entry.constBegin(); + for (; it2 != entry.constEnd(); it2++) + { + switch ((*it2).m_uds) { + case KIO::UDSEntry::UDS_NAME: + displayName = (*it2).m_str; + break; + case KIO::UDSEntry::UDS_URL: + url = (*it2).m_str; + break; + case KIO::UDSEntry::UDS_SIZE: + size = (*it2).m_long; + break; + } + } + QCOMPARE(size, 123456ULL); + QCOMPARE(displayName, QStringLiteral("name")); + QVERIFY(url.isEmpty()); + } +} + +// QHash or QMap? doesn't seem to make much difference. +typedef QHash UDSEntryHV; + +static void fillUDSEntryHV(UDSEntryHV &entry, const QDateTime &now, const QString &nameStr) +{ + entry.reserve(8); + entry.insert(KIO::UDSEntry::UDS_NAME, nameStr); + // we might need a method to make sure people use unsigned long long + entry.insert(KIO::UDSEntry::UDS_SIZE, 123456ULL); + entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, now); + entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, now); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0644); + entry.insert(KIO::UDSEntry::UDS_USER, nameStr); + entry.insert(KIO::UDSEntry::UDS_GROUP, nameStr); +} + +void UdsEntryBenchmark::testHashVariantSlave() +{ + const QDateTime now = QDateTime::currentDateTime(); + QBENCHMARK { + UDSEntryHV entry; + fillUDSEntryHV(entry, now, nameStr); + QCOMPARE(entry.count(), 8); + } +} + +void UdsEntryBenchmark::testHashVariantApp() +{ + // Normally the code would look like this, but let's change it to time it like the old api + /* + QString displayName = entry.value( KIO::UDSEntry::UDS_NAME ).toString(); + QUrl url = entry.value( KIO::UDSEntry::UDS_URL ).toString(); + KIO::filesize_t size = entry.value( KIO::UDSEntry::UDS_SIZE ).toULongLong(); + */ + UDSEntryHV entry; + fillUDSEntryHV(entry, now, nameStr); + + QString displayName; + KIO::filesize_t size; + QString url; + + QBENCHMARK { + // For a field that we assume to always be there + displayName = entry.value(KIO::UDSEntry::UDS_NAME).toString(); + + // For a field that might not be there + UDSEntryHV::const_iterator it = entry.constFind(KIO::UDSEntry::UDS_URL); + const UDSEntryHV::const_iterator end = entry.constEnd(); + if (it != end) + { + url = it.value().toString(); + } + + it = entry.constFind(KIO::UDSEntry::UDS_SIZE); + if (it != end) + { + size = it.value().toULongLong(); + } + + QCOMPARE(size, 123456ULL); + QCOMPARE(displayName, QStringLiteral("name")); + QVERIFY(url.isEmpty()); + } +} + +// The KDE4 solution: QHash+struct + +// Which one is used depends on UDS_STRING vs UDS_LONG +struct UDSAtom4 { // can't be a union due to qstring... + UDSAtom4() {} // for QHash or QMap + UDSAtom4(const QString &s) : m_str(s) {} + UDSAtom4(long long l) : m_long(l) {} + + QString m_str; + long long m_long; +}; + +// Another possibility, to save on QVariant costs +typedef QHash UDSEntryHS; // hash+struct + +static void fillQHashStructEntry(UDSEntryHS &entry, time_t now_time_t, const QString &nameStr) +{ + entry.reserve(8); + entry.insert(KIO::UDSEntry::UDS_NAME, nameStr); + entry.insert(KIO::UDSEntry::UDS_SIZE, 123456ULL); + entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, now_time_t); + entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, now_time_t); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0644); + entry.insert(KIO::UDSEntry::UDS_USER, nameStr); + entry.insert(KIO::UDSEntry::UDS_GROUP, nameStr); +} +void UdsEntryBenchmark::testHashStructSlave() +{ + QBENCHMARK { + UDSEntryHS entry; + fillQHashStructEntry(entry, now_time_t, nameStr); + QCOMPARE(entry.count(), 8); + } +} + +void UdsEntryBenchmark::testHashStructApp() +{ + UDSEntryHS entry; + fillQHashStructEntry(entry, now_time_t, nameStr); + + QString displayName; + KIO::filesize_t size; + QString url; + + QBENCHMARK { + // For a field that we assume to always be there + displayName = entry.value(KIO::UDSEntry::UDS_NAME).m_str; + + // For a field that might not be there + UDSEntryHS::const_iterator it = entry.constFind(KIO::UDSEntry::UDS_URL); + const UDSEntryHS::const_iterator end = entry.constEnd(); + if (it != end) + { + url = it.value().m_str; + } + + it = entry.constFind(KIO::UDSEntry::UDS_SIZE); + if (it != end) + { + size = it.value().m_long; + } + QCOMPARE(size, 123456ULL); + QCOMPARE(displayName, QStringLiteral("name")); + QVERIFY(url.isEmpty()); + } +} + +// Let's see if QMap makes any difference +typedef QMap UDSEntryMS; // map+struct + +static void fillQMapStructEntry(UDSEntryMS &entry, time_t now_time_t, const QString &nameStr) +{ + entry.insert(KIO::UDSEntry::UDS_NAME, nameStr); + entry.insert(KIO::UDSEntry::UDS_SIZE, 123456ULL); + entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, now_time_t); + entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, now_time_t); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0644); + entry.insert(KIO::UDSEntry::UDS_USER, nameStr); + entry.insert(KIO::UDSEntry::UDS_GROUP, nameStr); +} + +void UdsEntryBenchmark::testMapStructSlave() +{ + QBENCHMARK { + UDSEntryMS entry; + fillQMapStructEntry(entry, now_time_t, nameStr); + QCOMPARE(entry.count(), 8); + } +} + +void UdsEntryBenchmark::testMapStructApp() +{ + UDSEntryMS entry; + fillQMapStructEntry(entry, now_time_t, nameStr); + + QString displayName; + KIO::filesize_t size; + QString url; + + QBENCHMARK { + + // For a field that we assume to always be there + displayName = entry.value(KIO::UDSEntry::UDS_NAME).m_str; + + // For a field that might not be there + UDSEntryMS::const_iterator it = entry.constFind(KIO::UDSEntry::UDS_URL); + const UDSEntryMS::const_iterator end = entry.constEnd(); + if (it != end) + { + url = it.value().m_str; + } + + it = entry.constFind(KIO::UDSEntry::UDS_SIZE); + if (it != end) + { + size = it.value().m_long; + } + + QCOMPARE(size, 123456ULL); + QCOMPARE(displayName, QStringLiteral("name")); + QVERIFY(url.isEmpty()); + } +} + +// Frank's suggestion in https://git.reviewboard.kde.org/r/118452/ +class FrankUDSEntry +{ +public: + class Field + { + public: + inline Field(const QString &value) : m_str(value), m_long(0) {} + inline Field(long long value = 0) : m_long(value) { } + QString m_str; + long long m_long; + }; + QVector fields; + // If udsIndexes[i] == uds, then fields[i] contains the value for 'uds'. + QVector udsIndexes; + + void reserve(int size) + { + fields.reserve(size); + udsIndexes.reserve(size); + } + void insert(uint field, const QString &value) + { + const int index = udsIndexes.indexOf(field); + if (index >= 0) { + fields[index] = Field(value); + } else { + udsIndexes.append(field); + fields.append(Field(value)); + } + } + void insert(uint field, long long value) + { + const int index = udsIndexes.indexOf(field); + if (index >= 0) { + fields[index] = Field(value); + } else { + udsIndexes.append(field); + fields.append(Field(value)); + } + } + int count() const + { + return udsIndexes.count(); + } + QString stringValue(uint field) const + { + const int index = udsIndexes.indexOf(field); + if (index >= 0) { + return fields.at(index).m_str; + } else { + return QString(); + } + } + long long numberValue(uint field, long long defaultValue = -1) const + { + const int index = udsIndexes.indexOf(field); + if (index >= 0) { + return fields.at(index).m_long; + } else { + return defaultValue; + } + } +}; + +static void fillFrankUDSEntry(FrankUDSEntry &entry, time_t now_time_t, const QString &nameStr) +{ + entry.reserve(8); + entry.insert(KIO::UDSEntry::UDS_NAME, nameStr); + entry.insert(KIO::UDSEntry::UDS_SIZE, 123456ULL); + entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, now_time_t); + entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, now_time_t); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0644); + entry.insert(KIO::UDSEntry::UDS_USER, nameStr); + entry.insert(KIO::UDSEntry::UDS_GROUP, nameStr); +} + +void UdsEntryBenchmark::testTwoVectorsSlave() +{ + QBENCHMARK { + FrankUDSEntry entry; + fillFrankUDSEntry(entry, now_time_t, nameStr); + QCOMPARE(entry.count(), 8); + } +} + +void UdsEntryBenchmark::testTwoVectorsApp() +{ + FrankUDSEntry entry; + fillFrankUDSEntry(entry, now_time_t, nameStr); + + QString displayName; + KIO::filesize_t size; + QString url; + + QBENCHMARK { + displayName = entry.stringValue(KIO::UDSEntry::UDS_NAME); + url = entry.stringValue(KIO::UDSEntry::UDS_URL); + size = entry.numberValue(KIO::UDSEntry::UDS_SIZE); + QCOMPARE(size, 123456ULL); + QCOMPARE(displayName, QStringLiteral("name")); + QVERIFY(url.isEmpty()); + } +} + +QTEST_MAIN(UdsEntryBenchmark) + +#include "udsentry_benchmark.moc" diff --git a/autotests/udsentrytest.cpp b/autotests/udsentrytest.cpp new file mode 100644 index 0000000..8da7b6f --- /dev/null +++ b/autotests/udsentrytest.cpp @@ -0,0 +1,224 @@ +/* This file is part of the KDE project + Copyright (C) 2013 Frank Reininghaus + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "udsentrytest.h" + +#include +#include +#include + +#include + +struct UDSTestField { + UDSTestField() {} + + UDSTestField(uint uds, const QString &value) : + m_uds(uds), + m_string(value) + { + Q_ASSERT(uds & KIO::UDSEntry::UDS_STRING); + } + + UDSTestField(uint uds, long long value) : + m_uds(uds), + m_long(value) + { + Q_ASSERT(uds & KIO::UDSEntry::UDS_NUMBER); + } + + uint m_uds; + QString m_string; + long long m_long; +}; + +/** + * Test that storing UDSEntries to a stream and then re-loading them works. + */ +void UDSEntryTest::testSaveLoad() +{ + QVector > testCases; + QVector testCase; + + // Data for 1st UDSEntry. + testCase + << UDSTestField(KIO::UDSEntry::UDS_SIZE, 1) + << UDSTestField(KIO::UDSEntry::UDS_USER, QStringLiteral("user1")) + << UDSTestField(KIO::UDSEntry::UDS_GROUP, QStringLiteral("group1")) + << UDSTestField(KIO::UDSEntry::UDS_NAME, QStringLiteral("filename1")) + << UDSTestField(KIO::UDSEntry::UDS_MODIFICATION_TIME, 123456) + << UDSTestField(KIO::UDSEntry::UDS_CREATION_TIME, 12345) + << UDSTestField(KIO::UDSEntry::UDS_DEVICE_ID, 2) + << UDSTestField(KIO::UDSEntry::UDS_INODE, 56); + testCases << testCase; + + // 2nd entry: change some of the data. + testCase.clear(); + testCase + << UDSTestField(KIO::UDSEntry::UDS_SIZE, 2) + << UDSTestField(KIO::UDSEntry::UDS_USER, QStringLiteral("user2")) + << UDSTestField(KIO::UDSEntry::UDS_GROUP, QStringLiteral("group1")) + << UDSTestField(KIO::UDSEntry::UDS_NAME, QStringLiteral("filename2")) + << UDSTestField(KIO::UDSEntry::UDS_MODIFICATION_TIME, 12345) + << UDSTestField(KIO::UDSEntry::UDS_CREATION_TIME, 1234) + << UDSTestField(KIO::UDSEntry::UDS_DEVICE_ID, 87) + << UDSTestField(KIO::UDSEntry::UDS_INODE, 42); + testCases << testCase; + + // 3rd entry: keep the data, but change the order of the entries. + testCase.clear(); + testCase + << UDSTestField(KIO::UDSEntry::UDS_SIZE, 2) + << UDSTestField(KIO::UDSEntry::UDS_GROUP, QStringLiteral("group1")) + << UDSTestField(KIO::UDSEntry::UDS_USER, QStringLiteral("user2")) + << UDSTestField(KIO::UDSEntry::UDS_NAME, QStringLiteral("filename2")) + << UDSTestField(KIO::UDSEntry::UDS_MODIFICATION_TIME, 12345) + << UDSTestField(KIO::UDSEntry::UDS_DEVICE_ID, 87) + << UDSTestField(KIO::UDSEntry::UDS_INODE, 42) + << UDSTestField(KIO::UDSEntry::UDS_CREATION_TIME, 1234); + testCases << testCase; + + // 4th entry: change some of the data and the order of the entries. + testCase.clear(); + testCase + << UDSTestField(KIO::UDSEntry::UDS_SIZE, 2) + << UDSTestField(KIO::UDSEntry::UDS_USER, QStringLiteral("user4")) + << UDSTestField(KIO::UDSEntry::UDS_GROUP, QStringLiteral("group4")) + << UDSTestField(KIO::UDSEntry::UDS_MODIFICATION_TIME, 12346) + << UDSTestField(KIO::UDSEntry::UDS_DEVICE_ID, 87) + << UDSTestField(KIO::UDSEntry::UDS_INODE, 42) + << UDSTestField(KIO::UDSEntry::UDS_CREATION_TIME, 1235) + << UDSTestField(KIO::UDSEntry::UDS_NAME, QStringLiteral("filename4")); + testCases << testCase; + + // 5th entry: remove one field. + testCase.clear(); + testCase + << UDSTestField(KIO::UDSEntry::UDS_SIZE, 2) + << UDSTestField(KIO::UDSEntry::UDS_USER, QStringLiteral("user4")) + << UDSTestField(KIO::UDSEntry::UDS_GROUP, QStringLiteral("group4")) + << UDSTestField(KIO::UDSEntry::UDS_MODIFICATION_TIME, 12346) + << UDSTestField(KIO::UDSEntry::UDS_INODE, 42) + << UDSTestField(KIO::UDSEntry::UDS_CREATION_TIME, 1235) + << UDSTestField(KIO::UDSEntry::UDS_NAME, QStringLiteral("filename4")); + testCases << testCase; + + // 6th entry: add a new field, and change some others. + testCase.clear(); + testCase + << UDSTestField(KIO::UDSEntry::UDS_SIZE, 89) + << UDSTestField(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("icon6")) + << UDSTestField(KIO::UDSEntry::UDS_USER, QStringLiteral("user6")) + << UDSTestField(KIO::UDSEntry::UDS_GROUP, QStringLiteral("group4")) + << UDSTestField(KIO::UDSEntry::UDS_MODIFICATION_TIME, 12346) + << UDSTestField(KIO::UDSEntry::UDS_INODE, 32) + << UDSTestField(KIO::UDSEntry::UDS_CREATION_TIME, 1235) + << UDSTestField(KIO::UDSEntry::UDS_NAME, QStringLiteral("filename6")); + testCases << testCase; + + // Store the entries in a QByteArray. + QByteArray data; + { + QDataStream stream(&data, QIODevice::WriteOnly); + foreach (const QVector &testCase, testCases) { + KIO::UDSEntry entry; + + foreach (const UDSTestField &field, testCase) { + uint uds = field.m_uds; + if (uds & KIO::UDSEntry::UDS_STRING) { + entry.insert(uds, field.m_string); + } else { + Q_ASSERT(uds & KIO::UDSEntry::UDS_NUMBER); + entry.insert(uds, field.m_long); + } + } + + QCOMPARE(entry.count(), testCase.count()); + stream << entry; + } + } + + // Re-load the entries and compare with the data in testCases. + { + QDataStream stream(data); + foreach (const QVector &testCase, testCases) { + KIO::UDSEntry entry; + stream >> entry; + QCOMPARE(entry.count(), testCase.count()); + + foreach (const UDSTestField &field, testCase) { + uint uds = field.m_uds; + QVERIFY(entry.contains(uds)); + + if (uds & KIO::UDSEntry::UDS_STRING) { + QCOMPARE(entry.stringValue(uds), field.m_string); + } else { + Q_ASSERT(uds & KIO::UDSEntry::UDS_NUMBER); + QCOMPARE(entry.numberValue(uds), field.m_long); + } + } + } + } + + // Now: Store the fields manually in the order in which they appear in + // testCases, and re-load them. This ensures that loading the entries + // works no matter in which order the fields appear in the QByteArray. + data.clear(); + + { + QDataStream stream(&data, QIODevice::WriteOnly); + foreach (const QVector &testCase, testCases) { + stream << testCase.count(); + + foreach (const UDSTestField &field, testCase) { + uint uds = field.m_uds; + stream << uds; + + if (uds & KIO::UDSEntry::UDS_STRING) { + stream << field.m_string; + } else { + Q_ASSERT(uds & KIO::UDSEntry::UDS_NUMBER); + stream << field.m_long; + } + } + } + } + + { + QDataStream stream(data); + foreach (const QVector &testCase, testCases) { + KIO::UDSEntry entry; + stream >> entry; + QCOMPARE(entry.count(), testCase.count()); + + foreach (const UDSTestField &field, testCase) { + uint uds = field.m_uds; + QVERIFY(entry.contains(uds)); + + if (uds & KIO::UDSEntry::UDS_STRING) { + QCOMPARE(entry.stringValue(uds), field.m_string); + } else { + Q_ASSERT(uds & KIO::UDSEntry::UDS_NUMBER); + QCOMPARE(entry.numberValue(uds), field.m_long); + } + } + } + } +} + +QTEST_MAIN(UDSEntryTest) diff --git a/autotests/udsentrytest.h b/autotests/udsentrytest.h new file mode 100644 index 0000000..fab7065 --- /dev/null +++ b/autotests/udsentrytest.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE project + Copyright (C) 2013 Frank Reininghaus + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef UDSENTRYTEST_H +#define UDSENTRYTEST_H + +#include + +class UDSEntryTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testSaveLoad(); +}; + +#endif diff --git a/autotests/upurltest.cpp b/autotests/upurltest.cpp new file mode 100644 index 0000000..32ef640 --- /dev/null +++ b/autotests/upurltest.cpp @@ -0,0 +1,57 @@ +/* This file is part of the KDE libraries + Copyright (c) 2013 David Faure + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License or ( at + your option ) version 3 or, at the discretion of KDE e.V. ( which shall + act as a proxy as in section 14 of the GPLv3 ), any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include + +class KIOUpUrlTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void upUrl_data(); + void upUrl(); +}; + +void KIOUpUrlTest::upUrl_data() +{ + QTest::addColumn("url"); + QTest::addColumn("upUrl"); + + QTest::newRow("empty") << "" << ""; + QTest::newRow("ref") << "file:/home/dfaure/my#myref" << "file:///home/dfaure/"; + QTest::newRow("qt2") << "file:/opt/kde2/qt2/doc/html/showimg-main-cpp.html#QObject::connect" << "file:///opt/kde2/qt2/doc/html/"; + QTest::newRow("query") << "http://www.kde.org/cgi/test.cgi?hello:My Value" << "http://www.kde.org/cgi/test.cgi"; + QTest::newRow("ftp1") << "ftp://user%40host.com@ftp.host.com/var/www/" << "ftp://user%40host.com@ftp.host.com/var/"; + QTest::newRow("ftp2") << "ftp://user%40host.com@ftp.host.com/var/" << "ftp://user%40host.com@ftp.host.com/"; + QTest::newRow("ftp3") << "ftp://user%40host.com@ftp.host.com/" << "ftp://user%40host.com@ftp.host.com/"; // unchanged + QTest::newRow("relative") << "tmp" << ""; // Going up from a relative url is not supported (#170695) +} + +void KIOUpUrlTest::upUrl() +{ + QFETCH(QString, url); + QFETCH(QString, upUrl); + + QCOMPARE(KIO::upUrl(QUrl(url)).toString(), upUrl); +} + +QTEST_MAIN(KIOUpUrlTest) + +#include "upurltest.moc" diff --git a/autotests/wronglocalsizes.zip b/autotests/wronglocalsizes.zip new file mode 100644 index 0000000000000000000000000000000000000000..3f76738ba10d6a7d3e59275b2b7aeaef877a495d GIT binary patch literal 325 zcmWIWW@h1H00A~3Nkiu#=R)UT&!|u!=4B9NNXslLE-6VZE=kMGNevC*WMKBbrR@m9 zp%p9)FPT#iDpC?l61f84rttzzODs-FOD@i=Dg|ObplQXa$@zIHD8@wsjRIkqaR?<) z^B9@LnGx>Gec`IY2Xre4^8z6VFaq(CMi2|lwX6`=hE^~z1b8FN0l6%=)pIdFPzMO} jp_t +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +include(CheckIncludeFiles) + +check_include_files(attr/libattr.h HAVE_ATTR_LIBATTR_H) +check_include_files(sys/xattr.h HAVE_SYS_XATTR_H) +check_include_files(sys/acl.h HAVE_SYS_ACL_H) +check_include_files(acl/libacl.h HAVE_ACL_LIBACL_H) + +if (HAVE_ATTR_LIBATTR_H AND HAVE_SYS_XATTR_H AND HAVE_SYS_ACL_H AND HAVE_ACL_LIBACL_H) + set(ACL_HEADERS_FOUND TRUE) +endif (HAVE_ATTR_LIBATTR_H AND HAVE_SYS_XATTR_H AND HAVE_SYS_ACL_H AND HAVE_ACL_LIBACL_H) + +if (ACL_HEADERS_FOUND) + find_library(ACL_LIBS NAMES acl ) + + find_library(ATTR_LIBS NAMES attr ) +endif (ACL_HEADERS_FOUND) + +if (ACL_HEADERS_FOUND AND ACL_LIBS AND ATTR_LIBS) + set(ACL_FOUND TRUE) + set(ACL_LIBS ${ACL_LIBS} ${ATTR_LIBS}) + message(STATUS "Found ACL support: ${ACL_LIBS}") +endif (ACL_HEADERS_FOUND AND ACL_LIBS AND ATTR_LIBS) + +mark_as_advanced(ACL_LIBS ATTR_LIBS) + diff --git a/cmake/FindGSSAPI.cmake b/cmake/FindGSSAPI.cmake new file mode 100644 index 0000000..8facf8c --- /dev/null +++ b/cmake/FindGSSAPI.cmake @@ -0,0 +1,100 @@ +# - Try to detect the GSSAPI support +# Once done this will define +# +# GSSAPI_FOUND - system supports GSSAPI +# GSSAPI_INCS - the GSSAPI include directory +# GSSAPI_LIBS - the libraries needed to use GSSAPI +# GSSAPI_FLAVOR - the type of API - MIT or HEIMDAL + +# Copyright (c) 2006, Pino Toscano, +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + + +if(GSSAPI_LIBS AND GSSAPI_FLAVOR) + + # in cache already + set(GSSAPI_FOUND TRUE) + +else(GSSAPI_LIBS AND GSSAPI_FLAVOR) + + find_program(KRB5_CONFIG NAMES krb5-config PATHS + /opt/local/bin + /usr/lib/mit/bin # e.g. openSUSE + ONLY_CMAKE_FIND_ROOT_PATH # this is required when cross compiling with cmake 2.6 and ignored with cmake 2.4, Alex + ) + mark_as_advanced(KRB5_CONFIG) + + #reset vars + set(GSSAPI_INCS) + set(GSSAPI_LIBS) + set(GSSAPI_FLAVOR) + + if(KRB5_CONFIG) + + set(HAVE_KRB5_GSSAPI TRUE) + exec_program(${KRB5_CONFIG} ARGS --libs gssapi RETURN_VALUE _return_VALUE OUTPUT_VARIABLE GSSAPI_LIBS) + if(_return_VALUE) + message(STATUS "GSSAPI configure check failed.") + set(HAVE_KRB5_GSSAPI FALSE) + endif(_return_VALUE) + + exec_program(${KRB5_CONFIG} ARGS --cflags gssapi RETURN_VALUE _return_VALUE OUTPUT_VARIABLE GSSAPI_INCS) + string(REGEX REPLACE "(\r?\n)+$" "" GSSAPI_INCS "${GSSAPI_INCS}") + string(REGEX REPLACE " *-I" ";" GSSAPI_INCS "${GSSAPI_INCS}") + + exec_program(${KRB5_CONFIG} ARGS --vendor RETURN_VALUE _return_VALUE OUTPUT_VARIABLE gssapi_flavor_tmp) + set(GSSAPI_FLAVOR_MIT) + if(gssapi_flavor_tmp MATCHES ".*Massachusetts.*") + set(GSSAPI_FLAVOR "MIT") + else(gssapi_flavor_tmp MATCHES ".*Massachusetts.*") + set(GSSAPI_FLAVOR "HEIMDAL") + endif(gssapi_flavor_tmp MATCHES ".*Massachusetts.*") + + if(NOT HAVE_KRB5_GSSAPI) + if (gssapi_flavor_tmp MATCHES "Sun Microsystems.*") + message(STATUS "Solaris Kerberos does not have GSSAPI; this is normal.") + set(GSSAPI_LIBS) + set(GSSAPI_INCS) + else(gssapi_flavor_tmp MATCHES "Sun Microsystems.*") + message(WARNING "${KRB5_CONFIG} failed unexpectedly.") + endif(gssapi_flavor_tmp MATCHES "Sun Microsystems.*") + endif(NOT HAVE_KRB5_GSSAPI) + + if(GSSAPI_LIBS) # GSSAPI_INCS can be also empty, so don't rely on that + set(GSSAPI_FOUND TRUE) + message(STATUS "Found GSSAPI: ${GSSAPI_LIBS}") + + set(GSSAPI_INCS ${GSSAPI_INCS}) + set(GSSAPI_LIBS ${GSSAPI_LIBS}) + set(GSSAPI_FLAVOR ${GSSAPI_FLAVOR}) + + mark_as_advanced(GSSAPI_INCS GSSAPI_LIBS GSSAPI_FLAVOR) + + endif(GSSAPI_LIBS) + + endif(KRB5_CONFIG) + +endif(GSSAPI_LIBS AND GSSAPI_FLAVOR) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 0000000..350bce3 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(kcookiejar5) +add_subdirectory(kioslave5) diff --git a/docs/design.txt b/docs/design.txt new file mode 100644 index 0000000..d3b5d10 --- /dev/null +++ b/docs/design.txt @@ -0,0 +1,203 @@ +DESIGN: +======= + +libkio uses kioslaves (separate processes) that handle a given protocol. +Launching those slaves is taken care of by the kdeinit/klauncher tandem, +which are notified by DBus. + +Connection is the most low-level class, the one that encapsulates the pipe. + +SlaveInterface is the main class for transferring anything to the slave +and Slave, which inherits SlaveInterface, is the sub class that Job should handle. + +A slave inherits SlaveBase, which is the other half of SlaveInterface. + +The scheduling is supposed to be on a two level basis. One is in the daemon +and one is in the application. The daemon one (as opposite to the holy one? :) +will determine how many slaves are ok for this app to be opened and it will +also assign tasks to actually existing slaves. +The application will still have some kind of a scheduler, but it should be +a lot simpler as it doesn't have to decide anything besides which +task goes to which pool of slaves (related to the protocol/host/user/port) +and move tasks around. +Currently a design study to name it cool is in scheduler.cpp but in the +application side. This is just to test other things like recursive jobs +and signals/slots within SlaveInterface. If someone feels brave, the scheduler +is yours! +On a second thought: at the daemon side there is no real scheduler, but a +pool of slaves. So what we need is some kind of load calculation of the +scheduler in the application and load balancing in the daemon. + +A third thought: Maybe the daemon can just take care of a number of 'unused' +slaves. When an application needs a slave, it can request it from the daemon. +The application will get one, either from the pool of unused slaves, +or a new one will be created. This keeps things simple at the daemon level. +It is up to the application to give the slaves back to the daemon. +The scheduler in the application must take care not to request too many +slaves and could implement priorities. + +Thought on usage: +* Typically a single slave-type is used exclusively in one application. E.g. +http slaves are used in a web-browser. POP3 slaves used in a mail program. + +* Sometimes a single program can have multiple roles. E.g. konqueror is +both a web-browser and a file-manager. As a web-browser it primarily uses +http-slaves as a file-manager file-slaves. + +* Selecting a link in konqueror: konqueror does a partial download of +the file to check the mimetype (right??) then the application is +started which downloads the complete file. In this case it should +be able to pass the slave which does the partial download from konqueror +to the application where it can do the complete download. + +Do we need to have a hard limit on the number of slaves/host? +It seems so, because some protocols are about to fail if you +have two slaves running in parallel (e.g. POP3) +This has to be implemented in the daemon because only at daemon +level all the slaves are known. As a consequence slaves must +be returned to the daemon before connecting to another host. +(Returning the slaves back to the daemon after every job is not +strictly needed and only causes extra overhead) + +Instead of actually returning the slave to the daemon, it could +be enough to ask 'recycling permission' from the daemon: the +application asks the daemon whether it is ok to use a slave for +another host. The daemon can then update its administration of +which slave is connected to which host. + +The above does of course not apply to hostless protocols (like file). +(They will never change host). + +Apart from a 'hard limit' on the number of slaves/host we can have +a 'soft limit'. E.g. upon connection to a HTTP 1.1 server, the web- +server tells the slave the number of parallel connections allowed. +THe simplest solution seems to be to treat 'soft limits' the same +as 'hard limits'. This means that the slave has to communicate the +'soft limit' to the daemon. + +Jobs using multiple slaves. + +If a job needs multiple slaves in parallel (e.g. copying a file from +a web-server to a ftp-server or browsing a tar-file on a ftp-site) +we must make sure to request the daemon for all slaves together since +otherwise there is a risk of deadlock. + +(If two applications both need a 'pop3' and a 'ftp' slave for a single +job and only a single slave/host is allowed for pop3 and ftp, we must +prevent giving the single pop3 slave to application #1 and the single +ftp slave to application #2. Both applications will then wait till the +end of times till they get the other slave so that they can start the +job. (This is a quite unlikely situation, but nevertheless possible)) + + +File Operations: +listRecursive is implemented as listDir and finding out if in the result + is a directory. If there is, another listDir job is issued. As listDir + is a readonly operation it fails when a directory isn't readable + .. but the main job goes on and discards the error, because +bIgnoreSubJobsError is true, which is what we want (David) + +del is implemented as listRecursive, removing all files and removing all + empty directories. This basically means if one directory isn't readable + we don't remove it as listRecursive didn't find it. But the del will later + on try to remove it's parent directory and fail. But there are cases when + it would be possible to delete the dir in chmod the dir before. On the + other hand del("/") shouldn't list the whole file system and remove all + user owned files just to find out it can't remove everything else (this + basically means we have to take care of things we can remove before we try) + + ... Well, rm -rf / refuses to do anything, so we should just do the same: + use a listRecursive with bIgnoreSubJobsError = false. If anything can't + be removed, we just abort. (David) + + ... My concern was more that the fact we can list / doesn't mean we can + remove it. So we shouldn't remove everything we could list without checking + we can. But then the question arises how do we check whether we can remove it? + (Stephan) + + ... I was wrong, rm -rf /, even as a user, lists everything and removes + everything it can (don't try this at home!). I don't think we can do + better, unless we add a protocol-dependent "canDelete(path)", which is + _really_ not easy to implement, whatever protocol. (David) + + +Lib docu +======== + +mkdir: ... + +rmdir: ... + +chmod: ... + +special: ... + +stat: ... + +get is implemented as TransferJob. Clients get 'data' signals with the data. +A data block of zero size indicates end of data (EOD) + +put is implemented as TransferJob. Clients have to connect to the +'dataReq' signal. The slave will call you when it needs your data. + +mimetype: ... + +file_copy: copies a single file, either using CMD_COPY if the slave + supports that or get & put otherwise. + +file_move: moves a single file, either using CMD_RENAME if the slave + supports that, CMD_COPY + del otherwise, or eventually + get & put & del. + +file_delete: delete a single file. + +copy: copies a file or directory, recursively if the latter + +move: moves a file or directory, recursively if the latter + +del: deletes a file or directory, recursively if the latter + +Resuming +-------- +If a .part file exists, KIO offers to resume the download. +This requires negociation between the kioslave that reads +(handled by the get job) and the kioslave that writes +(handled by the put job). + +Here's how the negociation goes. +(PJ=put-job, GJ=get-job) + +PJ can't resume: +PJ-->app: canResume(0) (emitted by dataReq) +GJ-->app: data() +PJ-->app: dataReq() +app->PJ: data() + +PJ can resume but GJ can't resume: +PJ-->app: canResume(xx) +app->GJ: start job with "resume=xxx" metadata. +GJ-->app: data() +PJ-->app: dataReq() +app->PJ: data() + +PJ can resume and GJ can resume: +PJ-->app: canResume(xx) +app->GJ: start job with "resume=xxx" metadata. +GJ-->app: canResume(xx) +GJ-->app: data() +PJ-->app: dataReq() +app->PJ: canResume(xx) +app->PJ: data() + +So when the slave supports resume for "put" it has to check after the first +dataRequest() whether it has got a canResume() back from the app. If it did +it must resume. Otherwise it must start from 0. + +Protocols +========= + +Most KIO slaves (but not all) are implementing internet protocols. +In this case, the slave name matches the URI name for the protocol. +A list of such URIs can be found here, as per RFC 4395: +http://www.iana.org/assignments/uri-schemes.html + diff --git a/docs/kcookiejar5/CMakeLists.txt b/docs/kcookiejar5/CMakeLists.txt new file mode 100644 index 0000000..cd93618 --- /dev/null +++ b/docs/kcookiejar5/CMakeLists.txt @@ -0,0 +1 @@ +kdoctools_create_manpage(man-kcookiejar5.8.docbook 8 INSTALL_DESTINATION ${KDE_INSTALL_MANDIR}) diff --git a/docs/kcookiejar5/man-kcookiejar5.8.docbook b/docs/kcookiejar5/man-kcookiejar5.8.docbook new file mode 100644 index 0000000..827d5ec --- /dev/null +++ b/docs/kcookiejar5/man-kcookiejar5.8.docbook @@ -0,0 +1,149 @@ + + +]> + + + + +kcookiejar5 User's Manual + +Waldo + Bastian + +
bastian@kde.org
+
+
+ + +Dawit +Alemayehu + +
adawit@kde.org
+
+
+ +2015-07-31 +Frameworks 5.13 + +
+ + +kcookiejar5 +8 + + + +kcookiejar5 +Command line interface to the &kde; HTTP cookie daemon + + + +Synopsis + + +kcookiejar5 + +-h, --help +-v, --version +--shutdown +--remove domain +--remove-all +--reload-config + + + + +Description + +kcookiejar5 is a command line interface to the HTTP cookie store used by KDE, +a D-BUS service to store/retrieve/clean cookies. + + + + +Options + + + + + + +Show help about options. + + + + + + +Show version information + + + + + + +Shut down cookie jar and the D-BUS service. + + + + + + domain + + +Removes cookies for domain from the cookie jar. + + + + + + + + +Removes all the cookies from the cookie jar. + + + + + + + +Reloads the configuration file. + + + + + + + + + +Usage +kcookiejar5 is a command line tool to access the kded module which manages cookies in Konqueror and other KDE applications. + +When started without parameters it loads the kded module to provide the dbus interface to store cookies. + + +When kcookiejar5 is started with some parameters, it does +additional tasks to the cookies jar it provides, like removing the cookies from one +domain. + + + + + +See Also +kf5options(7), qt5options(7) + + + + +Bugs +Please use KDE's bugtracker to report bugs. + + +
+ + + diff --git a/docs/kioslave5/CMakeLists.txt b/docs/kioslave5/CMakeLists.txt new file mode 100644 index 0000000..127af91 --- /dev/null +++ b/docs/kioslave5/CMakeLists.txt @@ -0,0 +1,9 @@ +add_subdirectory(data) +add_subdirectory(file) +add_subdirectory(ftp) +add_subdirectory(help) +add_subdirectory(http) +add_subdirectory(mailto) +add_subdirectory(telnet) +add_subdirectory(webdav) + diff --git a/docs/kioslave5/data/CMakeLists.txt b/docs/kioslave5/data/CMakeLists.txt new file mode 100644 index 0000000..3c5edda --- /dev/null +++ b/docs/kioslave5/data/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/data) diff --git a/docs/kioslave5/data/index.docbook b/docs/kioslave5/data/index.docbook new file mode 100644 index 0000000..2cb24b4 --- /dev/null +++ b/docs/kioslave5/data/index.docbook @@ -0,0 +1,54 @@ + + + +]> + +
+Data URLs + + + +LeoSavernik +
l.savernik@aon.at
+
+ +
+ +2003-02-06 + + +
+ +Data URLs allow small document data to be included in the URL itself. +This is useful for very small HTML testcases or other occasions that do not +justify a document of their own. + +data:,foobar +(note the comma after the colon) will deliver a text document that contains +nothing but foobar + + +The last example delivered a text document. For HTML documents one +has to specify the MIME type text/html: +data:text/html,<title>Testcase</title><p>This +is a testcase</p>. This will produce exactly the same +output as if the content had been loaded from a document of its own. + + +Specifying alternate character sets is also possible. Note that 8-Bit +characters have to be escaped by a percentage sign and their two-digit +hexadecimal codes: +data:;charset=iso-8859-1,Gr%FC%DFe aus Schl%E4gl +results in +Grüße aus Schlägl +whereas omitting the charset attribute might lead to something like +Gr??e aus Schl?gl + + +IETF +RFC2397 provides more information. + +
+ diff --git a/docs/kioslave5/file/CMakeLists.txt b/docs/kioslave5/file/CMakeLists.txt new file mode 100644 index 0000000..bdcc8dd --- /dev/null +++ b/docs/kioslave5/file/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/file) diff --git a/docs/kioslave5/file/index.docbook b/docs/kioslave5/file/index.docbook new file mode 100644 index 0000000..83f4f38 --- /dev/null +++ b/docs/kioslave5/file/index.docbook @@ -0,0 +1,27 @@ + + + +]> + +
+file + + +&Ferdinand.Gassauer; &Ferdinand.Gassauer.mail; + + + + + +The file protocol is used by all &kde; applications to +display locally available files. + + +Entering +file:/directoryname in &konqueror; + lists the files of this folder. + + +
diff --git a/docs/kioslave5/ftp/CMakeLists.txt b/docs/kioslave5/ftp/CMakeLists.txt new file mode 100644 index 0000000..2a6b6dd --- /dev/null +++ b/docs/kioslave5/ftp/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/ftp) diff --git a/docs/kioslave5/ftp/index.docbook b/docs/kioslave5/ftp/index.docbook new file mode 100644 index 0000000..6e7e475 --- /dev/null +++ b/docs/kioslave5/ftp/index.docbook @@ -0,0 +1,50 @@ + + + +]> + +
+&FTP; + + +&Lauri.Watts; &Lauri.Watts.mail; + + + + + +&FTP; is the Internet service used to transfer a data file from the disk of +one computer to the disk of another, regardless of the operating system type. + + + Similar to other Internet applications, &FTP; uses the +client-server approach — a user invokes an &FTP; program on the +computer, instructs it to contact a remote computer, and then requests +the transfer of one or more files. The local &FTP; program becomes a +client that uses TCP to contact an &FTP; server +program on the remote computer. Each time the user requests a file +transfer, the client and the server programs cooperate to send a copy +of the data across the Internet. + + &FTP; servers which allow anonymous &FTP; permit +any user, not only users with accounts on the host, to browse the +ftp archives and download files. Some &FTP; servers are +configured to allow users to upload files. + + +&FTP; is commonly used to retrieve information and obtain software stored in +files at &FTP; archive sites throughout the world. + + + + +Source: Paraphrased from +http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm + + + See the manual: ftp. + +
diff --git a/docs/kioslave5/help/CMakeLists.txt b/docs/kioslave5/help/CMakeLists.txt new file mode 100644 index 0000000..98cb154 --- /dev/null +++ b/docs/kioslave5/help/CMakeLists.txt @@ -0,0 +1,4 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/help) + +add_subdirectory(documentationnotfound) diff --git a/docs/kioslave5/help/documentationnotfound/CMakeLists.txt b/docs/kioslave5/help/documentationnotfound/CMakeLists.txt new file mode 100644 index 0000000..22394d6 --- /dev/null +++ b/docs/kioslave5/help/documentationnotfound/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/help/documentationnotfound) diff --git a/docs/kioslave5/help/documentationnotfound/index.docbook b/docs/kioslave5/help/documentationnotfound/index.docbook new file mode 100644 index 0000000..c7d6822 --- /dev/null +++ b/docs/kioslave5/help/documentationnotfound/index.docbook @@ -0,0 +1,69 @@ + + + +]> +
+Documentation not Found + + +Jack +Ostroff + +
ostroffjh@users.sourceforge.net
+
+
+ +
+ +2014-04-02 +Frameworks 5.0 + +
+ +The requested documentation was not found on your computer. + +The documentation may not exist, or it may not have been installed +with the application. + + +How to solve this issue + +If the application is &kde; software, start by searching the KDE Documentation site for the requested +documentation. If you find the documentation on that site, your distribution +might ship a separate package for documentation (⪚ called plasma-doc for +documentation related to &plasma;). Please use the package manager of your +distribution to find and install the missing documentation. + +If you use a source based distribution, such as Gentoo, be sure that +there are not any configuration settings (USE flags in Gentoo) that +might have disabled the installation of the documentation. + + +If you have done that, but still get this page displayed instead of the +application handbook, you probably found a bug in the help +system. In this case, please report this on the KDE Bug Tracker under the KIO product. + + +If you do not find any documentation on the KDE Documentation site, the +application may not have offline documentation. Please report this on +the KDE Bug Tracker under the +product for the application. + + +In case the application does not have offline documentation, you should +use the online resources UserBase Documentation and +KDE Community Forums to get +help. + + +For non-&kde; applications, please contact the application author to +determine whether there should be offline documentation available. + + +
diff --git a/docs/kioslave5/help/index.docbook b/docs/kioslave5/help/index.docbook new file mode 100644 index 0000000..3c5d822 --- /dev/null +++ b/docs/kioslave5/help/index.docbook @@ -0,0 +1,24 @@ + + + +]> + +
+help + + +&Ferdinand.Gassauer;&Ferdinand.Gassauer.mail; + + + + + +The help system of &kde; + + + See The &khelpcenter;. + + +
diff --git a/docs/kioslave5/http/CMakeLists.txt b/docs/kioslave5/http/CMakeLists.txt new file mode 100644 index 0000000..efeaa02 --- /dev/null +++ b/docs/kioslave5/http/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/http) diff --git a/docs/kioslave5/http/index.docbook b/docs/kioslave5/http/index.docbook new file mode 100644 index 0000000..c3fbef8 --- /dev/null +++ b/docs/kioslave5/http/index.docbook @@ -0,0 +1,35 @@ + + + +]> + +
+http / https + + +&Lauri.Watts; &Lauri.Watts.mail; + + + + +http is the +HyperText +Transfer Protocol. + +The http kioslave is used by all &kde; applications to handle +connections to http servers, that is, web servers. The most common +usage is to view web pages in the &konqueror; web browser. + +You can use the http kioslave in &konqueror; by giving it a +URL. +http://www.kde.org. + +https is http encapsulated in a SSL/TLS stream. + +SSL is the Secure Sockets Layer protocol, a security protocol that provides communications privacy over the Internet. The protocol allows client/server applications to communicate in a way that is designed to prevent eavesdropping, tampering, or message forgery. + +TLS stands for Transport Layer Security. + +
diff --git a/docs/kioslave5/mailto/CMakeLists.txt b/docs/kioslave5/mailto/CMakeLists.txt new file mode 100644 index 0000000..96185b8 --- /dev/null +++ b/docs/kioslave5/mailto/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/mailto) diff --git a/docs/kioslave5/mailto/index.docbook b/docs/kioslave5/mailto/index.docbook new file mode 100644 index 0000000..2878ece --- /dev/null +++ b/docs/kioslave5/mailto/index.docbook @@ -0,0 +1,87 @@ + + + +]> + +
+mailto + + + +Christopher +Yeleighton +giecrilj@stegny.2a.pl + + + + +2012-01-28 +&kde; 4.8 + + +The kioslave mailto is responsible for launching the mail composer of your +choice when you open a &URL; in the mailto scheme (RFC6068). + + + + +Syntax +The syntax of a mailto &URL; follows the following pattern: + +mailto:recipients?query + +where recipients form a list of restricted &SMTP; address specifications, and the +query part may contain one or more of the following parameters: + + + +&to=recipients +Specifies additional recipients. + + + +&cc=recipients +Specifies additional recipients of carbon copies. + + + +&bcc=recipients +Specifies additional recipients of blind carbon copies. These recipients +will receive the message, but all other recipients will not know about that. + + + +&body=text +Specifies the text of the message. This text should not be long, as there +may be hard limits on how long an &URL; may be. + + + +&subject=text +Specifies the subject of the message. + + + + + + + + +Example + +mailto:info@kde.org?cc=kde@kde.org&subject=Thank%20you!&body=KDE%20rocks!%20How%20can%20I%20help%3F + + + + +Configuration + +Choose the application to handle mailto locators in &systemsettings; Workspace Appearance and BehaviourDefault Applications. + + + + + +
diff --git a/docs/kioslave5/telnet/CMakeLists.txt b/docs/kioslave5/telnet/CMakeLists.txt new file mode 100644 index 0000000..067fd33 --- /dev/null +++ b/docs/kioslave5/telnet/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/telnet) diff --git a/docs/kioslave5/telnet/index.docbook b/docs/kioslave5/telnet/index.docbook new file mode 100644 index 0000000..d1bda3d --- /dev/null +++ b/docs/kioslave5/telnet/index.docbook @@ -0,0 +1,24 @@ + + + +]> + +
+telnet + + +&Ferdinand.Gassauer; &Ferdinand.Gassauer.mail; + + + + +The network terminal protocol (TELNET) allows a user to log in on any other computer on the network supporting TELNET. + + + + See the manual: telnet. + + +
diff --git a/docs/kioslave5/webdav/CMakeLists.txt b/docs/kioslave5/webdav/CMakeLists.txt new file mode 100644 index 0000000..a767168 --- /dev/null +++ b/docs/kioslave5/webdav/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/webdav) diff --git a/docs/kioslave5/webdav/index.docbook b/docs/kioslave5/webdav/index.docbook new file mode 100644 index 0000000..35f1b99 --- /dev/null +++ b/docs/kioslave5/webdav/index.docbook @@ -0,0 +1,73 @@ + + + +]> + +
+webdav / webdavs + + +&Hamish.Rodda; &Hamish.Rodda.mail; + + + +2002-01-21 + +WebDAV is a Distributed +Authoring and Versioning +protocol for the World Wide Web. It allows for easy management of +documents and scripts on a http server, and has +additional features designed to simplify version management amongst +multiple authors. + +Usage of this protocol is simple. Type the location you want to +view, similar to a http URL except for the +webdav:// protocol name at the start. An example is +webdav://www.hostname.com/path/. +If you specify a folder name, a list of files and folders will be +displayed, and you can manipulate these folders and files just as you +would with any other filesystem. + + +WebDAV Features + +Locking + +File locking allows users to lock a file, informing others that they +are +currently working on this file. This way, editing can be done without fear +that +the changes may be overwritten by another person who is also editing the +same +document. + + + +Source file access + +WebDAV allows access to the script which is called +to +produce a specific page, so changes can be made to the script itself. + + + +Per-document property support + +Arbitrary properties may be set to assist identification of a +document, +such as the author. + + + + +To take advantage of these additional capabilities, you will need an +application which supports them. No application currently supports them +through +this kioslave. + +WebDAVS is the WebDAV protocol encrypted via SSL. + +
diff --git a/docs/krun-passing-slaves.txt b/docs/krun-passing-slaves.txt new file mode 100644 index 0000000..c513d6f --- /dev/null +++ b/docs/krun-passing-slaves.txt @@ -0,0 +1,28 @@ +konq_run / krun should determine the mimetype by actually +getting the contents of the URL. It should then put the slave +on hold and tell the job-scheduler which request the +slave is currently handling. + +Now krun/konq_run should determine which client should process the +result of the request. + +* When the client belongs to the same process, no action needs to be +taken. When a new job is created for the request which is on hold the +existing slave will be re-used and the request resumed. + +* When the client is an external process, the on-hold-slave should be +removed from the job-scheduler and should connect itself with +klauncher. This is hard because it must ensure that the external +program does not request the slave before it has been transfered to +klauncher. + +* When a slave is on hold but not used for a certain period of time, +or, when another slave is put on hold, the slave should be killed. + +===== + +The slave must emit "mimetype" during a GET before the first data is send. + +It may wait with sending "mimetype" until it has enough data to +determine the mimetype, but it should not pass any data along before it has +sent the mimetype. diff --git a/docs/metadata.txt b/docs/metadata.txt new file mode 100644 index 0000000..36a4b89 --- /dev/null +++ b/docs/metadata.txt @@ -0,0 +1,188 @@ +METADATA +======== + +Applications can provide "metadata" to the slaves. Metadata can influence +the behavior of a slave and is usally protocol dependent. MetaData consists +of two strings: a "key" and a "value". + +Any meta data whose "key" starts with the keywords {internal~currenthost} and +"{internal~allhosts}" will be treated as internal metadata and will not be made +available to client applications. Instead all such meta-data will be stored and +sent back to the appropriate ioslaves along with the other regular metadata values. + +Use "{internal~currenthost}" to make the internal metadata available to all +ioslaves of the same protocol and host as the ioslave that generated it. If +you do not want to restrict the availability of the internal metadata to only +the current host, then use {internal~allhosts}. In either case the internal +metadata follows the rules of the regular metadata and therefore cannot be sent +from one protocol such as "http" to a completely different one like "ftp". + +Please note that when internal meta-data values are sent back to ioslaves, the +keyword used to mark them internal will be stripped from the key name. + +The following keys are currently in use: + +Key Value(s) Description +---- -------- ----------- + +referrer string The URL from which the request originates. (read by http) + +modified string The modification date of the document (set by http and by kio before put) + +accept string List of mimetypes to accept separated by a ", ". (read by http) + +responsecode string Original response code of the web server. (set by http) + +SendUserAgent bool Whether to send a User-Agent (read by http) +UserAgent string The user agent name to send to remote host (read by http) + +content-type string The content type of the data to be uploaded (read and set by http) +media-* string Media-Parameter attributes (e.g. media-boundary) +media-*-kio-quoted bool The corresponding media- attribute's value was quoted. (set by http) + +cache "cache" Use entry from cache if available. + "cacheonly" Do not do any remote lookups, fail if not in cache. (read by http) + "verify" Use entry from cache, verify with remote server if expired + "refresh" Use entry from cache after verifying with remote server + "reload" Do not do any cache lookups. + +no-cache bool Flag that indicates whether caching is enabled/disabled + +window-id number winId() of the window the request is associated with. + +range-start number Try to get the file starting at the given offset (set by file_copy when finding a .part file, + but can also be set by apps.) + +range-end number Try to get the file until at the given offset (not set in kdelibs; handled by kio_http). + +resume number Deprecated compatibility name for range-start +resume_until number Deprecated compatibility name for range-end + +charset string Charset of the current content as returned by a HTTP Header Response. + +Charsets string Charset(s) send in the "Accept-Charset:" HTTP Request Header. + +Languages string Language(s) send in the "Accept-Language:" HTTP Request Header. + +content-disposition-type string Type of Content-Disposition from a HTTP Header Response. +content-disposition-* any other valid value sent in a Content-Disposition header (e.g. filename) + +request-id number Sequence number to identify requests in a MultiGet command. + +expire-date number Date on which a cache entry needs validation. + +cache-creation-date number Date on which a cache entry has been created. + +http-refresh string Passes HTTP Refresh meta-data back to the application. + +cookies "auto" Use kcookiejar to lookup and collect cookies (default) + "manual" Cookies set in "setcookies" are send, received cookies are reported + via "setcookies". + "none" No cookies are sent, received cookies are discarded. + +setcookies string Used to send/receive HTTP cookies when "cookies" is set to "manual". + +errorPage bool Flag that indicates that an errorPage() is preferred over an error(). (default:true) + +no-auth bool Flag that indicates that no authentication (neither WWW nor proxy) attempts should be made. +no-www-auth bool Flag that indicates that no HTTP WWW authentication attempts should be made. +no-proxy-auth bool Flag that indicates that no HTTP proxy authentication attempts should be made. +no-auth-prompt bool Flag that indicates that only cached authentication tokens should be used. +no-preemptive-auth-reuse bool Flag that indicates whether cached credentials should be preemptively sent to the server. + +ssl_activate_warnings bool Flag that disables SSL warning dialogs if set to false. (default: true) + +ssl_was_in_use bool Flag to tell TCPSlaveBase if SSL was in use in the previous transaction. + (default: false) + +ssl_in_use bool Set in TCPSlaveBase to tell the caller if SSL is in use. + (default: assume false) + +ssl_using_client_cert bool Set in TCPSlaveBase to tell the caller if the session is using a client certificate (default: assume false) + +ssl_no_client_cert bool Flag to tell TCPSlaveBase if it should, under no circumstances, use a + client certificate. (default: false) + +ssl_demand_certificate bool Flag to tell TCPSlaveBase to demand that a client certificate is used for this connection. (default: false) + +ssl_no_ui bool Flag to tell TCPSlave that no user interaction should take place. Instead of asking security questions the connection will silently fail. This is of particular use to favicon code. (default: false) + +ssl_cipher string Set in TCPSlaveBase to tell the caller which cipher is currently being used. + This string is composed of the encryption, authentication, key-exchange and digest + methods separated by an LF (\n). + +ssl_cipher_name string Set in TCPSlaveBase to tell the caller the name of the cipher used. + +ssl_cipher_desc string Set in TCPSlaveBase to describe the details of the current cipher being used. + +ssl_cipher_version string Set in TCPSlaveBase to describe the version of the cipher being used. + +ssl_cipher_used_bits integer Set in TCPSlaveBase to relay the number of bits of the key actually being used in this cipher and connection. + +ssl_cipher_bits integer Set in TCPSlaveBase to relay the number of bits the key is capable of in this cipher and connection. + +ssl_peer_ip string Set in TCPSlaveBase to tell the caller the IP address of the peer. + +ssl_cert_state integer Set in TCPSlaveBase to relay the state of the certificate check, without considering the cache settings. Can be checked with KSSLCertificate enumeration. + +ssl_peer_certificate string Set in TCPSlaveBase to relay the base64 encoding of the X.509 certificate presented by the peer. + +ssl_peer_chain string Set, if present, in TCPSlaveBase to relay the entire certificate chain presented by the peer. The is base64 encoded and \n delimited. + +ssl_parent_ip string Set in TCPSlaveBase and in the caller. If this is the parent frame of a frame of the session (really only applies to https), this variable is set so that it can be passed back to the child frames. It is necessary to send it to child frames so that they can do a full certificate check. + +ssl_parent_cert string Set in TCPSlaveBase and in the caller. As above, this must be passed to child frames by the caller so that it can compare against the certificate presented in the child frames. It is a base64 encoding of the X.509 presented. + +ssl_session_id string Set in TCPSlaveBase to indicate the SSL session ID in base64 encoded ASN.1 encoded binary format. Also set in the caller to indicate to TCPSlaveBase to reuse a particular session ID. + +main_frame_request bool Actually for SSL, this is set in the caller to tell TCPSlaveBase if this is the request for the main frame of an html page. (dfault: true) + +HTTP-Version string The HTTP version in use for kio_http (set by http) + +PropagateHttpHeader bool Whether HTTP headers should be send back (read by http) + +HTTP-Headers string The HTTP headers, concatenated, \n delimited (set by http) + Requires PropagateHttpHeader to be set. + +customHTTPHeader string Custom HTTP headers to add to the request (read by http) + +CustomHTTPMethod string Overrides the method string sent by kio_http + without changing the behavior (read by http) + +UseProxy string URL representing the proxy settings (read by http) +ProxyUrls string a comma separated list of proxy urls. The first url in this list matches one set in "UseProxy". + +PrivacyPolicy stringlist \n delimited URIs referring to P3P privacy + policies presented by the HTTP server + +PrivacyCompactPolicy stringlist \n delimited P3P compact tag policies + presented by the HTTP server + +textmode bool When true, switches FTP up/downloads to ascii transfer mode (read by ftp) + +recurse bool When true, del() will be able to delete non-empty directories. (read by file) + Otherwise, del() is supposed to give an error on non-empty directories. + +DefaultRemoteProtocol string Protocol to redirect file:/// URLs to, default is "smb" (read by file) +no-spoof-check bool Flag to indicate whether a username spoofing check should be performed, default is FALSE.(read by http) +redirect-to-get bool If "true", changes a redrirection request to a GET operation regardless of the original operation. + +** NOTE: Anything in quotes ("") under Value(s) indicates literal value. + + +Examples: + +E.g. the following disables cookies: +job = KIO::get( QUrl("http://www.kde.org") ); +job->addMetaData("cookies", "none"); + +If you want to handle cookies yourself, you can do: +job = KIO::get( QUrl("http://www.kde.org") ); +job->addMetaData("cookies", "manual"); +job->addMetaData("setcookies", "Cookie: foo=bar; gnat=gnork"); + +The above sends two cookies along with the request, any cookies send back by +the server can be retrieved with job->queryMetaData("cookies") after +receiving the mimetype() signal or when the job is finished. + +The cookiejar is not used in this case. diff --git a/docs/pics/kpropertiesdialog.png b/docs/pics/kpropertiesdialog.png new file mode 100644 index 0000000000000000000000000000000000000000..ac3fc2b524feab208e40abcaf9ead48337c00e03 GIT binary patch literal 14213 zcmajG1ymf((=SW{3GNnLgF8VsSO`vF(V)RSNMLa%XmIz%-QC?;+}*Q4a1AbZd7k(F z-+S+SzVq$bnbX@-)ze*7Q}vtb+F(VX6gmnq3LG39x{S2AG928i53s`@={2l}kMT?|iEE~@IXbeQItdgFPZ zK6B!D?a-k&e!{(4k^H($7$^7xi-c{AGy;9FI5ocvt#7P2H8z+Qr%Q%98VQIKEdJlu zaVGY@}r!d!VY#;zYCX1(>Y=`uS71x2v<)U_^cyzJ58;TqD|@fy7`JbF_}*CbDa zLxed$E-r3?d@2DJmZGw<^0#lTKh}kHbaeDD$Zgt+i&2c)u)#V)kpXgA?A#5N8zWQ7 zP-yLr+ZJnFM_YWtS60ntE2QvsKPu;d;$m85xxRc%Z|=&8lM8l5h9V0rx3Ah#3TD@s z#c1Ii_7^9pvy$m8`Zczj$3pEtaMMbgjG*1OmwPH=Vm(PoNk0Qf_-rmOE=X)_2?z+N zqsGc9qCU3Lm6y*?O*N;16VkM!lH;9kq&xkQamm%xa`suv+OxK;dT*&dcwq*v;1@Ho z5=N2>xedh?)j4vN=jI%^bJsK_<%JXzuO-$x3wQ6Pl;)|#XE{1JD4&=j&k_?81q4di z!lN&y7$4CPj+K^{GOFamP5umMYincIZS}c$xV?D1zfMg`dPhRy?&=CL=EAG5uTM!y zIbLa+?(BrqAL#FI&wtN0U!f=BxIa@}&1ID|Ix=#KV{d=q@9&@06?`Zfc$T7}98_Bp z&C4h&o_(8TV76&q`68~$z(^wQ{^8+!2WftF5HnzOwFkGxf1G74aRcsco%|?SF&b+S zH>*man)?*3OTX{sp#*jGR&{f8b89OprCnrN0Wj_S>dNckhVvmuD$@5O z2B6hDIy(BaLbvUs{m#gI0~!iSabe-FhzQ*Vo7Gr)1x0ckoNDvQ?5wWExyj|VvtRSY zy~=3nI@5zX^>x?8B;&!q!fdP$77y+bZzLsaR#ybc)l}`n?V9F>NiyUijC=MwNmloN zjQqR|M6Na~yh|!Xrh8{Dm;TazU}InrrS2A0*RJ>SFE<_Mb+U65qxbuQ7p|?<;(W4Z zX>R_(a*hQvvM$7~sfyzV114r9^oSsUi5R3_xZg4*+Zjx?vMSURbF}On5SZS_5-$>W z3LFkZPgQKu|M@k(vAn9tb2!18jxN7ZK}DqomD5i4k!D1oh^Q4C@bRP3WIf(!q(UP^ z0Fy~e%;;b+fSZxgqCbo%$9!CMh9o-`RVD@W%kmagGMl6ZEG-XHq_RJ zQHVV8uu6p!_q{?6?lYuxJKYfF6nvp{2mz7VNP5#bWwwQ(1=2m{*AE9W4HZhK! zm}r6qkEkShPuO(FU_R=k`g&eT>=ZB_Lg1l3y+XRj#m?x@01Zq4KkS)TNEd|Q z8-7KwA_b8~q3FdYbai#j&CO{@s3e`)!E$Qr~OqGZ>bxXq8P9`C!H&T#l|ugT>y%HgG$ z;ujZKzBSJrPb)mTa5Dxph)=$2cs0{hUinS`z37w?3*7X|R3E;fmy3YXYE;YTuYK<^ zISEKPoOVWAymwm&$$pm2ri$uefIFUJ};f;dlg++9!!P8{DtuU{SsP8&sA z2GV!VywePpyF}`l+W6>A%&K2izrD4(TlRN%e?B+LAT!v+ZtP`=k5fAbe#t3Cuba9v zy?J^yAbvqedL|NrS!A!oX+Js|e%kM%Bq~FVrNbYE%AzgTrwcB~~xzoI;9-@#YgEu?2apY?sv6hi*cl_|({& z4?M!yLCIfZKHhj`c<=2Tb2f@k-VDPdCN8oL5OMYQIoaerGOgLUwglv4zc|5T5r#Kl z+liWp2$)OVnXN;IE!JRQSb(+jZ5@MuA1n@fv zb8^)WR)JCWUu86qZ?J4 z7k$Ql1-xX0>4G={O_B|)RWZxqj~mJCqDZUq4y5jvQ&Mh`8Lwik1_XcgQP7>n!7Bc+ z5Y@sY_p2Egn%kVdp$fl=+KH4~-u}iTh7s*0UT=oJM<`h8WGu6Mn_-M1b_@dmH-Ye7 zMZC6ivL+j`at4Ds=n)v}wv_y{Z-Ea4B>&+f!9XZ*V-OHX{rwNoCs@3o^|Kn)DP`~zzEVZFf{v@lTf0&!qS=LO=tAf5mJPT$yn z`2HIVDXa5~1^qnDIK79BZO;Kdd}dmKdz#gQ0TkQwp1qTC5J*Fpyaf~>AyMV~Iw7hP zgDfU-rLpVk$cS03a(g(rjJ2lT^kRooMa9&7@bYq@*1~E_oYxd>dWzWB&eqh#s`Kf`^<406Xvw(ooY~SEM7D^xM6^5Yc$>_3g zkOWpER6;IhW>bSMIk~tw1(<|v+b#Jc$&AjiUiTjDY1ePSmMt_9q|O@Buci-i6*(!a(p5-v+#>*d(1Ow7|=IODnI-9 ziW7YfPS6+sL=}ZF;@9fLmug%Sd3dn4dcShG=2bJW0EP05!G$+iIHN=H+FPr+h*C4XT_Tw(EzKWqy%pw-&Cv=J`=N-I648_P|K z5%f8$-nLN(I7V`5-({=uRaHU>ilmI7gcpGH6UnP1BeK&DT;Vr2rxp`S32>Jhv-QV9 zG2AiB7{sK5D6Hd}e^=~IVJonq^GilfN|pmUS*DIkf^cZfDu#juorZ~BS14T{C>=|? zs}Y{K62fJmsp}H ztsI$kUDS+w)rPN|4k8l;Zlk0hIsAh%wWRtr*}X&X3}n}Djt>&HON3~!OM?U-KON=+ z3vfY(t?uj5+|L@fLy?h+O$eyuo+62{$&iqI#)|RyAKZh^XetAkXJek7#uj+Z4pVoc zEC6iYGBxIrLje#MH-%1=%r*|o$oLbznZrUDmOogaW?LH_w8ZoM9BX&W72V0~+JL(` zcFv|jTi_23jDu$@2DigNHhJ=&bI8@O4 z>0u<9d+{n=$UR>!x!M?l6aG#S)c+$tdoZd-KSR*25S-$JvAlD=X1?<}s-~sO&Bb}^ zmxOmTx2LsNXsF)JX4mndo4~I0CuKJldVw}GP(pnCYDiNZwq!VQozXzVRh<934bZ-+ zla2L0$VQhYl7d>{@x5<>?e!@7K>HRxK1lL??XTn!IWv~KP%wMN@IZMx`);K{&)~=_ zdb0-u+`?kgIcQkZ&^teS*N-D;5j?eQNk(?=uYEI9A#OesLI=i|xPVQAO`@D>lFIt? zHwF8L+K`=R}!VpFHBdv{jJ11537wkzI&ZSiM);rA zG{FlbH8GH;?*mGWZKl_DGY$#U`W(=m9mAH!#+$o?iUB@we7xgH+cmX9*L1RocRM)% z7YBy{guxH^{lmH%e04##FYR}dOf;K7modQnOoczK(wGd{sm1}uMGZA?Q@twvia2q0=({2*X)E7+%it@D%S zT%B>IB#Nc7b>7EY;&t2(A@#2TMel_h39;H7XQv(;7|cC@ZDS6(nLJph{AA>*vqqMI zg?|{&d$Tczkj*f>)cr6>a`UCRlEDn?Bu6(22WgQV`#_-ha7+pTA3N{ICTIH&gR`qs zqMV>a+q&wH%hjXLp_)Xq+V0ueXL8~A&p030QBhDRcPFyMc1Kealalyd&!M<5gfL^; z7lIFaVJ7D`9m$pml^%P=R#1R?2&ZQ@fml-bbA->2kEHZR+ofk4GNdH&+MSjt-Htvq z7qOoSBIXe(x1;MJ?sW*f&9a*+UGCc@2a%0A-@`wC9vd6#;-uRh|CT+q#9VpKU#&?0^t;h*y8F2{yhP=5=?cv{%(%%3&9vocum)T-NIobUnf3vIo=fI^7zM!55(=0x!aC&CZT!rzEI6j`YN0{)1~)$`YcWNC@sIG$ zyb*R8xWD=`S7vFsAI^$_Na92yk)ehB3Nv`Uc&47eGrJKD$FqpdD9Ov=?>!0-eBt`0Lh0aq-a5Pzcg?TBq#$LwYz1bSkFW zKt(G6zQmcc1QyoP!xaIieX!fOG+-O0me#;j!c~GsH}dB36%`gYdG+SW1W1Q;B61g} z_HHczs``~Dv&SE`zV0Bo2M>(fY_nKu`4(f4lGE&%9UHx27Iu5iC&E-M}{a9lmFqZ+Gq+z0_AL*Uo z9{E{ZR`wSM0YX#(PN6#*|0+rBe=iAG)21tTX*%NT*^XnDzXu&00T8b>D4^0VI#OI0k9`%-_j|od$S7>XvXqRQszH(H|YaCH?t&L z;IlC2<{H06JzsggEo`b?*fMvt z6|`g_?p~Y1VSmxi$9Y&UlQ4bY&SlMiTNc`N{>;z zr&n~xtatfrVZq3TAz`rd^mxySyznwDOz5FwW&P;Y(K0s|7Zx`}4u&Fw!HbKF6i*1H zx^2XsH5{=^$q0}|TmUC6+!xm4)fQ*HAAXiwLpSoA?4nWNNe~XqQefhd`R9ZMYhb}J z*2@tE{^ta{90UG`=KrL70Zji*$eARDCGIawUm%Gkjv#A^>^r3eL&-2qO9_y9IU)aZ zKxv=G*OK9+zUJDI;)_vcW8)#HiYs;AOMW?R?+aB{ufk);>V=PV6z$uNv!`MPQQ&kF zQIg{Lllo8lBE&@5gA|VaKVj*pv(6ViM*nEAOmc8-G$qpQfdMXusxs_{CI?|YFYi)5sDhaA=!p~$1}MB4EBH6;2iaM+sbzry zTeLY5>d#Ef6=wh_ejEb;Kg5m7%=&0N&*Wa7p;}#B>ZEe_?{+L`Fqk4>KZSklGeCthKohQ$^DAavZLAbkx_+twS_bE^^u$ zW3aHX(`xBMfB8#<^3Kf6d>&g`%9tyaVPGkyoD?$N#p6{39WOVW1bqZMuerkVxY7Kh zgT@3xYy8ICL0*i}_AJV}0*A#~7GDw(u9kvDz%F1+Sg|n%_4_C`49Zz=x(6F#D z2!xexRmgY|g}7OB88*(sg2qLiUCi{N@t>u-gHp|!m1#tD;+Yb4b}^tEUj>^v(267K z91#vsQeuf3I)pr&_@lVAG?|wbblf_d%12Ko{D2yi6uq+@KY|%a=VvlFGIq@kYQNhr zq42uN>NOirf4un}IlZ)0XE|S>K%vDdbUYN#oOk_xIWJMy?W~Vb0IS} zqFbzlf&F7TKm1!g46XlY`#UG$s8Ol^3Kti{Ztm{yflwQ^z1niUb<-ZqDam+j1`sk( z(K(BX{?v=DDc;u?H6SvOUD%3=8LF*)Xl(4Anqq2c>$YKMW)rR!{=jHua+OX7wZkOR zboG)1qH*Zb9!1`yIj=Pt_MuodNp`J;EZ zZZISaimS-V$|B*lq68AzwBMR4K%vws`EpdkM*U&jWBh(2%C{r2!5Zz@0L!IXi(W!j zt=_!X7SPhtk4bupvKrM6J%@`WD5#NGQ>M&gqpX{V@i#ZQb(4^iU3p;ok`S|7474=I zq^NhA+x~I96ecolLUvub*k)+Vmp_rNUAr0H?EtsOzw;HEH9K916s;S+s2^v4mN%OW zCwN)Ut=W$YVG5ph(nJb!!Q3TXR08R8tp#q4)O4fs;X;?vt2$cO2v6#UB!GQB?auag zn(Iz7hyRfA0&QO>E`Tc6A!Wq$JvPiwrd&((o`>7cX_}=;oIxW-Q+QBmL29bxgTw66 z97g@;(KKcf64q;tcJs}S+D1msKbDh88a&j78prTyeC?kX=>Xrq>5LiL%W6qRJV(dA z74NuTb3gHXy4^kZbv@YmGpnxmxazzu&$A?p+mAUp!-{_H}ik`2h5%98$oz4e+$p9dk9MrvXwscC7} zTFc!5sKFIFEeZ9?nwkK&OLH$K{VCsot1*ES!H3hHF;nZ#s93~YMkCkrn=pwm&@uG# zsaGjSlBmY%bfe38-IFi4udi=Jnn)*@JJ@`^y$#J-gDuQurYsR!>qavNg;pzn_NF(} zynOa;a3dVdm67bJR2kwPHh#4YVy~}NwU-%b9*7VI<$KIJ4aY!Ys7&+{o{zA$c-Ebkty}-*=oWvT>>Sio z){3`g7L;7zKPg!?S_Z@jd{nD-3bWjKM|PP(L^bK%A{#+hV0uRcprN6us)jlP-WOz3 z#m0KpXfD1Pz&J3AT;^?UqoJO`o?AJ)&X1_fZ0xdzPOQbDiO|sF#*~(_B*xL8CZR~c z)6i>w=dL-AB+1T?3_n^Q;_S#Xl05C&?7Bew8+9oJ*=s)2$tolS5iAaKb6vC|Gq)27 zoUoIrUs-e4=#8_I%*hx|t$lc3@L7o+%EH3*Wqj~2fcAK?;3W(OfZs@%SnL6WgL-pS zUr6*`>tT)TX1>-#36M2n0^pEN{z z@}AClJ@3crVK$oO?L@tHSWbno$k4)Djj&ejqj1}u7>#7u%Zqyv7A@u7)lsM^=9Xz; zt5(9gnWvdOjSAqGCAIJnjl15LR`%YOMr;}m90NHyY1Mi@6p&ky+ein#;MIcB?Cfl= zT)H69d+o$%m6Xu!H>~jy3KbxV?7ET8ywAPD&{qP^$5xtN8GqA8!*fAcCxJ$Vd1;EQ z;bZ~?qr&BA&w8%g`0ve(s0yx&2YiT#S>HwT1G9oPVy36xu*O9wPz((ZV-Lon0CvIl zw45|Feo8Ycx1Q6^ynNXp7vsAYSo8_5SeuW5;_Z1h)y33QMNPZU8uf-oC!}*2yl#~s zS>pzjv72YjyWDl^aZqN+_qvWHCnvieH|@GHb^N#+1JQ^9pFX{bO`^K?|Gt+MM9u}! z)KCV!WdQ_yiIVk^-_G0T+*wT2^Ma_eXkS_pQGCk{k+-jE&Ex?n#A@E}r2d%dgUz=3 zLHIx*l_Ys+lMnw!Q$ypACo5-goTx?^0_2uS-Awpv0hHxV6P_f)COS6dwiuf?HDy|LGeNHGm^ zUap(hy+3X_8EV%vvs$cvgUORJ0E3TK9?lhl#7-?b4~Q9V`;4G!vBv7^DJv3FzxJ4E zLQr);$~B+uMkg$*vT5fl#xaH!t{6qf%8Ks&rPW%0Z23or{QGxR)n1Qx!6H8QnvH=I zX|OEJ<{PJZ9p@%n_zd=|(P>-xuQdv3U%Ir<_&T2h$u1_+bxvGR8wru=@yDuJQr)xp zO#DU~8EEHzGE2{g(K9{yOpxGIXQYe6y99jsE-sGH&f_xcaXBfqr6ybThJ!Q+gd!u6 z70hY1z>%J4H71W*CheCu zw=ZUxpv#&@OpQb+0V5+LmpQEZpfL)UevdxG11B0mCO9})l>hUf#ftf8Xb?Obp&5Ug zuxE2YwI&^TgVoId5nK zF=c_FTkXb%KDkOW?F?-tUL3eR49w3tX%ZL|^o12Q>=`?)A&Jd=lLIfxhBe&iTiXLg z5}GM9;I)WHwvN6G@Q>(n9=lPefrl+UJv#AcS(F{*^Eb!PTr=asE|62SUAKF}*-*8W zF0EEvc**7vpMEFY+pjkCga!AVI^<=su-7AR7!>Tbb=H43Ev?wb!8pZ>CCAxD5?BTB zPeE{q=O-^PRsp4R0JhT%ZYSC%@7F-Xtdo`gu=D249_um?nCmLcq^!Afb!8jj15X?G)}N^#=Gy&4^yIT?A^$$%jq#xhM>+?FLDyZCgZ-vmrtA%>T-ADjIz z4`s)2>F9^yOC|XWDqY?Y5$(wVFIx(2uWce61K<^7GT8EBTFsx3l$I>6C+Nl z`e^|D=zYYw-#blHFaIeN0)n|$8AyrA@s%)~UcHhB6*K^yyK%Tz0yR}LwKUr@wK$dh z!KOr=rIp&UzY%;iTg=AE<=~>tv0bR?jWAAv(QLzhQd3d=6c;XH+aFB>&miwm@bvTV zeFjF`Y~hY^FhK6Y;l3o{t^G1tjL_B}u8vNQp=4xWj}D5VO40RUd|xAPr9h*Z&8ALoHBL|8o;|zM8zb)O0Ye!X(X)&Fz zI4`Y3Oj#8KY$&KEYw1iMlI)>;GqIUsqV3fHRmZ}){W>>7g{+xhi$5dYBD_7sRqWDy& zv+UonFL3vM_zEe4qJ8qDRMUL8!(RBA>uwqUU$EiDjX#^5geg%YcXPP;Nhna^z9SB! zBp1V?IpKI0@@wN2-6@W5QSzi4h60EBLcyE9iN8wAa>}1O2%Da5@atFCRULmkD{z9T z4f!R|c7ft(1-~{wfT@R(%=8>_-+m~QY1I|5!X=@JhbYMVmaAM4LKKVN_zhZD_qJ`LQ$|&P(0ATrlL7mj7|sD!4U`+dr(%_ zx__q|YQIZ_VlD6N?A+_~B>4bWH#RGiAWQPeTRQIgQeMI}{o4pm=I0OOfAK}n;4ZL; zaK_sAcPbSpH*or&j%}-Zsa7|)*w{xW8#VB$Xv6DDkj=<(wuL-Zl)&@^IS7R~|J-9 z?8%P?Hg8oLnvHSI`g3E-oTs-JF^uDeioRBb^yic!6^lS^ee8arA;Tnead{~oMDLvo zlvUEg3z~3i+u5Sg@z?@(YbIdy&l%2O^try}wVnn*@H^IhZ|?47^?2^v#>dA&l=gwN zt_tg_al65N46pXxNUEs|pQ3tR^;2=tf<0%^8%WvN*=IL;W28IHUzg95?m9~H*ElhZ zaAPb*U$>Fd)6@TP)yUyZMGp$I35DMbeZ~krPcQ#xo=UH0FwOL^@h~chwoYm($j3^X z4I*Xv4}@?wjpJ!lmyha&P13N(O36?z!r*zj&d%)4XzjvS+yn^?+U_pknW?UWJVfEu zUz8z!rr@2f5r&iL#eW}?>EaVh6=?6mU$eh+c2;F$>1x$E-M5ACyGr0Wy#+fReGJKL z0=D4(=3huS3VlO-PF`NR2eCON>juOz+d_`gc+a`6AWm7IyUmf1OgE%WxM?gRV%Y39 z_YH!2xJlBHsR9|pYY@WPuy!#+(J%#f{JG78rUZ?2#7ox%kD&Pxh_o!<(rgn>IM5;I z=^trBytf34VCME{Ybv2AenV7b`PT=KRV2i@i@VOD!sNL|Fr(G@PbV| zO$~n6qBK4cXQM$`DzeWX{n2)v1q6S5nLblw^L<5Y8H@qz4ZOIoNr*HS-pk0y@K)e= zMB2mBa^nYFU;YIRH94r2%s#z6yu6Hynh+Y9T2jj<`n7d*#414?@n_jM*qj-WA;95< z!;rs87FK^GD;PDPzs_7doRgE2-M_(d9=-CyVw8y=B{?ybA;0UT4fw>ED<&N@fztyi z6Da_zy#B^HKR=JRzP6@8>m08LF{Pa9>+5n9qu~Ky#xMRmZvy-=kBR`r-zS)0G@Bpo z?d>k)pYX0bxSzf&H?_7}jeasQyFWYoDdi{6wLD+XrSk6`vU_e%PkpMKK-~)lF$&-K zPfku$7d-8l2aS4U0>~^Z-*$iU%gwP8hxISE*T=)h!6f8>#L5QleS5Oz6;Lyl^Q(H* zFpI{YB}tIioOu!r;eqfqey!MwSoPcgkl(`e@D2#m1NDpiiT~63R}=r};sq-7RXE3LpxEeWO%>QMggK z&^TNu0$>{f@Cw%V>EGTfSkKGP|APwQe-Qn{_;2q&lrY}^ceww_Z=mo$sa|e6wgkAy zEG%aU6LIJ@VUXCGLaJs8&14}SK(m|geIH>uVxIk*mQ7~1kt7^@z>Y7lQJgHckroAs z*BbQ*hYt@yLOgRX|Jti<@Cb823|gE$e28p58LW6H_qpYz z4jNVg^iPfS|1Kx`PmT0g!uvK|_MOI4BzCuE)FQ%$lZDx#aFN$tkgr^kw6r$2Oo2K5 zquRF~*vYfAnA%Nc9ynl975HT5VoSjOTj31vk8(rq*WbSk|EL2UgcCe4A_0%Pf+3#w zHu82dZ=}j*2{*BI;pk0`0!g`b4+Dj zZ@~>VU-?+;wd$<~3a+o^iZzH$+FgdeeX|**;Q#|nlJHakquMRzYSAGQ)g~G}DM-Nb zZcQae%vMdI&mWX2LeB(%pd_v6Xu&C*39sDx^ z8}sd3t;<6mUTk zlUJaTL*y#ZcCqZ;)ZARYUU|C`P}@_5oMb>F72YptxHEfz)>C;$eF+&H**RNL_oy0r zGQj_yhR)AiaxA1`Ay05Y^y)j^V5vaE_NFuU$VK2gWrVxmuX_-&c`n36O^@V6Ml0J( zKk0waJY?VCTz0zpGig6Cwn{k7bxpw!-P|NM{fy=O&_kVhs`FV(dPwzfE zo5X$I?Oz2VN}LP>bW`Kg6ZCk8`*mc$z26W}fv3{WZI)`1Llfpu@UzYj>NRl&qv*t%T1kRmI$D^ZwCc>#%jgV_M& zkjMBCc2SbH{tTHKM4a~g&~sNwjorN1K^-+TllIZEyE&7phme$l*9yT`{dkyY9iEPR5iZj=}<8sq^uYu1Irl>XBx7x}^f$5}c$65jnTzuDHT@`;7 zZj<@RX{u)j{&q73-B@UL->_sjxt}r|zmiB>YwIq%#^IkquDrRoU_R#K4=O*x`|hB< z*{)*bHfb6SW=p?$#371qr6$)N0R_04T(mRBMZr4{Se;X9sF8};|C|`9iOT&#MKbU9l9bOiot$jX=Q*mkT7}xu= zx7ec29yysaur?T*so*A=U^!oou}c0#LCfESLO)hiwQS?~T~xA>VX9~fB6Ea?MS|x< z+5NsJfRh_I3k2=~x&3RZp-q2w3OkmXSy@=JWk3h>%cpL^u+@2bc=-8dw1*mAcV|VT z;uDkDCakxvdO&9Js~{fJ_dh9XY>rNw^WZ5)&*$5>i-21>$Yi8klsk}dq2ysFv6~6tF>MwqEhI#LG2uo%9er{@pZ323Ta&3tPMWVgK3RL&uG+KYP zHdq>>3(NRwAh^&Yf+JYj;#gn~jo%I!{xxq_w(R)#&7yU#brMP-Cs$a2D={9Y&Xx`# zN`kbiIo~6S_w}Qfo?Zu?OsRfO_8M@!F(f%eL%C2qUgPQ(B}g)1=qv9A3v*Q<-DN2)%{&dpV*}AcXDLRr)s)SFMOX(^#!69Id7><4gIUN;MN5tFP7<7>b zOLg=iBO1>1!1j%3ehGAA9urQv_;2<|H8-{Wj5feiAS5KDWzW;Ib3EFgIND6}eer|* z1#^}CZ+~F-HG*wlB|q0C*$Mw?6?Rq0k`0*CGm{&@Ao~b@y7dr~EaUN)68CEQ?zptg z?4wdNN0n@=x^Z~uao9Nc6#bqn51s$ZaB)ge!^hYZii!+k-4;0a8U;((Q%th{*!79f z#5j|hE#0xwZ8zBDXsNCx%B_jY{^_&ZKm-|T?vNu1?;C`-WjYO`#|l4bq!6VHnOcyCyv+!TGdE4Ady_u^osq*P>5(UBcT0Hfer9`TwT z7yO}17(61a&r~#kAB&li#Va%$NtvO}dTQ%Txg*O=!SwtfCOEU%iWuGDjkEIfL}HD% zmL5V&)tpuHm@Z^SPKltTkzge7k(TzdqDEL!nCbhU-C=cVI31O>j1b~q{PYcQpQz1A zcL@ip%o1p&B|T0DG!C_lcz%k}aRhUUBnvtB{!%iRZHs%wPuASpF@4R4cp}t<38lO}kGM5O^y&TPU~ixLw23AsK+B$Q0takT4t@8w-1F`-jca?{ zDS|O!$huRrgMs7GFkCk+E;9qqTxi^bV^oTpPBl3y^Ou66ZDQxMpltyI+$l{lFY;P6!n)BFyLx?)3VC`t`-7^#XX*;=|jxgi|!f zZ)`4wVp0&SqFk-h*rOMHYP^*6ANpj~u-kG$&5?fvRfK9I1=-Zfrn;(os$d?qCc;1# zoYse$n91!m^_nG`2^khQgYCc1!2X^SmG(5YW5<|Wu+7BErnfc{`ZtDV7awowpFv&v zZ*jICB8LXfX2c%xU;mUXYuLda#Cd~)sSw4Oc1LKEjvym2+NxpxII2UbgawQ7xVtk9 zk))308sFi^?j(M;7hRy_4igS<{f?8Zww%lu} xyIj?R)n4m)(EsNjY5!Jq|KA|*KkzK*lA`t&_nli~W7P{PmXQF8mw(px{a-^jW2*oF literal 0 HcmV?d00001 diff --git a/docs/pics/kurlrequester.png b/docs/pics/kurlrequester.png new file mode 100644 index 0000000000000000000000000000000000000000..9907e749b4e5c9c226060fa71ef53dd06aac506d GIT binary patch literal 1996 zcmaKtdpOgN9>>2p$t6F|SQm;^Dy-zvTxO!kWim=+Kb8Bom`ih?B>h|)d)%f_Dps~( z$u=?4b<$d!d#%mnc0w*mvl5Q;oadZBfB$@*&+DJh^L{>`=lQ(yyuDmC)O6JV0MKxE zb3P3K3ZRYNT19cAB2S!l0f5aV?#@oW$)edRf~PN9H^-@D;Mg=y!?Zph1t~6NNfMg1 znArSnT6CcX`>13vfyyqcFKAree)O!+#CylHhCNOJ$X6bt+%+yU*^NEwhOA}-`%neURD4EDoh1H1OPZ7 z0}43m0e3ZkzrpnMrPM1tWCod33%xjGy1Z?D^R;CC%(gf!vB)WD& zs7ySG3xqq=yf!BaF|d35SQT}3g5sKhH6=@i@nr^TEcQ&i`=_Vk{oy$QzlCsRkz=I~ zt)Oo;!~EkNT~>l<3_bpsy*ZV8G5NmB5+;YM6^LWUNE^Km*84?DvxJa5FqzJjPj)q2Lp8Trl>*Sr94SLiWX@l408~{;+^`lhs!K2Esa$lKu1ySP^X5> zQz#536rUPTwEd0`wd9>=HMv+z)*;r(9SVhGztbBZ=HP7)Gx;t{faH}H3_Y4 z*mB5+Hn+*Aa<6M)6`VjGidY=)o*810h(uyx;nT!fU5&>O4(=2CmL=M&+5rxSOFqX6 z@A4)pHaIa+#^@YdoZ!t2Hq@LoH#cvIkv>pWQc~h1&Q`z)P`Kc2nJKocj(oQu@No6f zNV4;u9qrz}zB&aL+mnlwm99OT3TgPe0zW$^z})s~B+pnXL-#igaC#l@iXM;s2}-A(g-N(q+K zU>RpM(?N5>DnOB!GNb}DbCK$?~;TH=+Res_fh>5V_l?bi}1H1R^*h_Nv&WP zjJ!0m8LMhUrBcJg!%G(iTto|J7u->AFrib^4@{55q;r{<#k4en-<_YMEu{BHTE%C_vBIbrPc~)5E zf$UOMBwyq0sFeX^O0>Rpbd~T4^9l%if?l`_#?@Sun9*KG_6dcK1p76&qhW@INCZMV z;*yna5%fP7w-DWI@M=>mVGF!x#y}1O*z-}9EF^flJpZ%IO?epp{IGaiu4KVx58q;r z^5D(3$OA5j!rSB?(5*l)1P6^4pF4BLG8dYXlJa60+f}%W3A_K?kku^^kZ#<-b0p0C z_b2@7&l%E;;#0!Ijc$#7OU{Pu8$W=&o@({4 z`wL-=Y12B*DB6hMPPO($_n22E0<#M}+59Lq(i1QkZ+f_iwpf{n>g^rWhu|g!)l#YS zST2;Q{poQykGR9~OBj6b-WobR(hc{QCI|z*{kAXoRdkCw%UPtg6!&6!fS$9FWqe?` zgMbx@Tk%-Zf!=I}BJTJ}y_%1Wr)T_jg;{hys#=jbmF2*a%XucVSJ$_2zB;bAL!|Po z!ACZ@bHqLNv~O8hYiZz_2Fy@TF{eQ-4KXt1MmgQ9ECs0q%bz4eF?&zS z+nGgq;iKnjXV;Q`;zR0S_5od4(ZHuXqDJkE1F=>+cmg|#e ztR%QBXi({;8Z;z+Q-nuvLoKNkVNES=KHFV=@WP)`vfSzW{4kXysLMN5)-LD%2vBD@ zuRXONLrORPqAtbaLS@nKgaaLZ8y->VVZdBOU$#s_UAm5pcJdFkL!XobAvzL-quj?z z^$1ZbU8evxlp)+#*D|u9GV8QdWsL1yi82svrULxWaez=6z$pCi&)&l?l literal 0 HcmV?d00001 diff --git a/metainfo.yaml b/metainfo.yaml new file mode 100644 index 0000000..1146fd8 --- /dev/null +++ b/metainfo.yaml @@ -0,0 +1,28 @@ +maintainer: dfaure +description: Resource and network access abstraction +tier: 3 +type: solution +platforms: + - name: Linux + - name: Windows + - name: MacOSX +portingAid: false +deprecated: false +release: true +framework-dependencies: + # kio calls org.kde.kded5 via dbus + - KDED +libraries: + - qmake: KIOCore + cmake: "KF5::KIOCore" + - qmake: KIOFileWidgets + cmake: "KF5::KIOFileWidgets" + - qmake: KIOWidgets + cmake: "KF5::KIOWidgets" + - qmake: KNTLM + cmake: "KF5::KIONTLM" +cmakename: KF5KIO + +public_lib: true +group: Frameworks +subgroup: Tier 3 diff --git a/po/af/kio5.po b/po/af/kio5.po new file mode 100644 index 0000000..098df2d --- /dev/null +++ b/po/af/kio5.po @@ -0,0 +1,9457 @@ +# UTF-8 test:äëïöü +# Copyright (C) 2001, 2005 Free Software Foundation, Inc. +# Frikkie Thirion , 2001,2002. +# Kobus , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: kcmkio stable\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2016-11-01 07:34+0000\n" +"PO-Revision-Date: 2005-11-26 17:02+0200\n" +"Last-Translator: Kobus \n" +"Language-Team: AFRIKAANS \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.10.2\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "WEB-Vertaler (http://kde.af.org.za), Kobus Venter" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "frix@expertron.co.za, kabousv@therugby.co.za" + +#: core/chmodjob.cpp:202 +#, fuzzy, kde-format +#| msgid "Could not change permissions for %1." +msgid "Could not modify the ownership of file %1" +msgstr "Kon nie regte vir %1 verander nie." + +#: core/chmodjob.cpp:204 +#, kde-format +msgid "" +"Could not modify the ownership of file %1. You have insufficient " +"access to the file to perform the change." +msgstr "" +"Kon nie die eienaarskap van lêer %1 verander nie. Jy het " +"onvoldoende toegang na die lêer om die verandering aan te bring." + +#: core/connectionbackend.cpp:144 +#, kde-format +msgid "Unable to create io-slave: %1" +msgstr "Nie moontlik na skep io-slave: %1" + +#: core/copyjob.cpp:1126 core/job_error.cpp:514 +msgid "Folder Already Exists" +msgstr "Die Gids Bestaan Alreeds" + +#: core/copyjob.cpp:1397 core/copyjob.cpp:1961 core/filecopyjob.cpp:360 +#: core/job_error.cpp:504 widgets/paste.cpp:101 +msgid "File Already Exists" +msgstr "Die lêer Bestaan Alreeds" + +#: core/copyjob.cpp:1397 core/copyjob.cpp:1961 +msgid "Already Exists as Folder" +msgstr "Bestaan alreeds as 'n gids" + +#: core/global.cpp:74 +#, kde-format +msgctxt "size in bytes" +msgid "%1 B" +msgstr "" + +#: core/global.cpp:78 +#, fuzzy, kde-format +#| msgid " %1 kB/s " +msgctxt "size in 1000 bytes" +msgid "%1 kB" +msgstr " %1 kB/s " + +#: core/global.cpp:79 +#, fuzzy, kde-format +#| msgid "%1 MB" +msgctxt "size in 10^6 bytes" +msgid "%1 MB" +msgstr "%1 Mb" + +#: core/global.cpp:80 +#, fuzzy, kde-format +#| msgid "%1 GB" +msgctxt "size in 10^9 bytes" +msgid "%1 GB" +msgstr "%1 Gb" + +#: core/global.cpp:81 +#, fuzzy, kde-format +#| msgid "%1 TB" +msgctxt "size in 10^12 bytes" +msgid "%1 TB" +msgstr "%1 Tb" + +#: core/global.cpp:82 +#, kde-format +msgctxt "size in 10^15 bytes" +msgid "%1 PB" +msgstr "" + +#: core/global.cpp:83 +#, kde-format +msgctxt "size in 10^18 bytes" +msgid "%1 EB" +msgstr "" + +#: core/global.cpp:84 +#, kde-format +msgctxt "size in 10^21 bytes" +msgid "%1 ZB" +msgstr "" + +#: core/global.cpp:85 +#, kde-format +msgctxt "size in 10^24 bytes" +msgid "%1 YB" +msgstr "" + +#: core/global.cpp:89 +#, fuzzy, kde-format +#| msgid "%1 KB" +msgctxt "memory size in 1024 bytes" +msgid "%1 KB" +msgstr "%1 Kb" + +#: core/global.cpp:90 +#, fuzzy, kde-format +#| msgid "%1 MB" +msgctxt "memory size in 2^20 bytes" +msgid "%1 MB" +msgstr "%1 Mb" + +#: core/global.cpp:91 +#, fuzzy, kde-format +#| msgid "%1 GB" +msgctxt "memory size in 2^30 bytes" +msgid "%1 GB" +msgstr "%1 Gb" + +#: core/global.cpp:92 +#, fuzzy, kde-format +#| msgid "%1 TB" +msgctxt "memory size in 2^40 bytes" +msgid "%1 TB" +msgstr "%1 Tb" + +#: core/global.cpp:93 +#, kde-format +msgctxt "memory size in 2^50 bytes" +msgid "%1 PB" +msgstr "" + +#: core/global.cpp:94 +#, kde-format +msgctxt "memory size in 2^60 bytes" +msgid "%1 EB" +msgstr "" + +#: core/global.cpp:95 +#, kde-format +msgctxt "memory size in 2^70 bytes" +msgid "%1 ZB" +msgstr "" + +#: core/global.cpp:96 +#, kde-format +msgctxt "memory size in 2^80 bytes" +msgid "%1 YB" +msgstr "" + +#: core/global.cpp:101 +#, fuzzy, kde-format +#| msgid "%1 KB" +msgctxt "size in 1024 bytes" +msgid "%1 KiB" +msgstr "%1 Kb" + +#: core/global.cpp:102 +#, fuzzy, kde-format +#| msgid "%1 MB" +msgctxt "size in 2^20 bytes" +msgid "%1 MiB" +msgstr "%1 Mb" + +#: core/global.cpp:103 +#, fuzzy, kde-format +#| msgid "%1 GB" +msgctxt "size in 2^30 bytes" +msgid "%1 GiB" +msgstr "%1 Gb" + +#: core/global.cpp:104 +#, fuzzy, kde-format +#| msgid "%1 TB" +msgctxt "size in 2^40 bytes" +msgid "%1 TiB" +msgstr "%1 Tb" + +#: core/global.cpp:105 +#, kde-format +msgctxt "size in 2^50 bytes" +msgid "%1 PiB" +msgstr "" + +#: core/global.cpp:106 +#, kde-format +msgctxt "size in 2^60 bytes" +msgid "%1 EiB" +msgstr "" + +#: core/global.cpp:107 +#, kde-format +msgctxt "size in 2^70 bytes" +msgid "%1 ZiB" +msgstr "" + +#: core/global.cpp:108 +#, kde-format +msgctxt "size in 2^80 bytes" +msgid "%1 YiB" +msgstr "" + +#: core/global.cpp:168 +#, fuzzy, kde-format +msgid "1 day %2" +msgid_plural "%1 days %2" +msgstr[0] "1 dag %2" +msgstr[1] "%1 dae %2" + +#: core/global.cpp:203 core/global.cpp:220 +#, fuzzy, kde-format +msgid "%1 Item" +msgid_plural "%1 Items" +msgstr[0] "1 lêer" +msgstr[1] "%1 lêers" + +#: core/global.cpp:207 +#, fuzzy, kde-format +#| msgid "%1 folder" +#| msgid_plural "%1 folders" +msgid "1 Folder" +msgid_plural "%1 Folders" +msgstr[0] "%1 gids" +msgstr[1] "%1 gidse" + +#: core/global.cpp:208 +#, fuzzy, kde-format +msgid "1 File" +msgid_plural "%1 Files" +msgstr[0] "1 lêer" +msgstr[1] "%1 lêers" + +#: core/global.cpp:211 +#, fuzzy, kde-format +msgctxt "folders, files (size)" +msgid "%1, %2 (%3)" +msgstr "%1 (%2)" + +#: core/global.cpp:212 +#, fuzzy, kde-format +#| msgid "%1 %" +msgctxt "folders, files" +msgid "%1, %2" +msgstr "%1 %" + +#: core/global.cpp:214 +#, fuzzy, kde-format +#| msgid "%1 (%2)" +msgctxt "files (size)" +msgid "%1 (%2)" +msgstr "%1 (%2)" + +#: core/global.cpp:221 +#, fuzzy, kde-format +#| msgid "%1 %" +msgctxt "items: folders, files (size)" +msgid "%1: %2" +msgstr "%1 %" + +#: core/job.cpp:110 +#, fuzzy +#| msgid "Moving" +msgctxt "@title job" +msgid "Moving" +msgstr "Beweeg" + +#: core/job.cpp:111 core/job.cpp:118 core/job.cpp:143 +#: widgets/fileundomanager.cpp:132 +#, fuzzy +msgctxt "The source of a file operation" +msgid "Source" +msgstr "Bron:" + +#: core/job.cpp:112 core/job.cpp:119 widgets/fileundomanager.cpp:133 +#, fuzzy +msgctxt "The destination of a file operation" +msgid "Destination" +msgstr "Bestemming:" + +#: core/job.cpp:117 +#, fuzzy +#| msgid "Copying" +msgctxt "@title job" +msgid "Copying" +msgstr "Besig om te kopiëer" + +#: core/job.cpp:124 +#, fuzzy +msgctxt "@title job" +msgid "Creating directory" +msgstr "Skep Gids" + +#: core/job.cpp:125 widgets/fileundomanager.cpp:127 +msgid "Directory" +msgstr "" + +#: core/job.cpp:130 +#, fuzzy +msgctxt "@title job" +msgid "Deleting" +msgstr " (Uitveeïng)" + +#: core/job.cpp:131 core/job.cpp:137 widgets/fileundomanager.cpp:138 +#, fuzzy +msgid "File" +msgstr "Filter:" + +#: core/job.cpp:136 +#, fuzzy +#| msgid "Examining" +msgctxt "@title job" +msgid "Examining" +msgstr "Ondersoek" + +#: core/job.cpp:142 +msgctxt "@title job" +msgid "Transferring" +msgstr "" + +#: core/job.cpp:148 +#, fuzzy +#| msgid "Mounting" +msgctxt "@title job" +msgid "Mounting" +msgstr "Besig om te heg" + +#: core/job.cpp:149 +#, fuzzy +msgid "Device" +msgstr "Toestel:" + +#: core/job.cpp:150 core/job.cpp:156 +#, fuzzy +msgid "Mountpoint" +msgstr "Mount punt:" + +#: core/job.cpp:155 +#, fuzzy +#| msgid "Unmounting" +msgctxt "@title job" +msgid "Unmounting" +msgstr "Besig om te ontkoppel" + +#: core/job_error.cpp:39 +#, kde-format +msgid "Could not read %1." +msgstr "Kon nie %1 lees nie" + +#: core/job_error.cpp:42 +#, kde-format +msgid "Could not write to %1." +msgstr "Kon nie na %1 skryf nie" + +#: core/job_error.cpp:45 +#, kde-format +msgid "Could not start process %1." +msgstr "Kon nie proses %1 begin nie" + +#: core/job_error.cpp:48 +#, kde-format +msgid "" +"Internal Error\n" +"Please send a full bug report at http://bugs.kde.org\n" +"%1" +msgstr "" +"Intern Fout\n" +"Asseblief stuur 'n volgrote fout raporteer na http://bugs.kde.org\n" +"%1" + +#: core/job_error.cpp:51 +#, kde-format +msgid "Malformed URL %1." +msgstr "Misformde URL %1" + +#: core/job_error.cpp:54 +#, kde-format +msgid "The protocol %1 is not supported." +msgstr "Die protokol %1 is nie ondersteunde." + +#: core/job_error.cpp:57 +#, kde-format +msgid "The protocol %1 is only a filter protocol." +msgstr "Die protokol %1 is slegs 'n filter protokol." + +#: core/job_error.cpp:64 +#, kde-format +msgid "%1 is a folder, but a file was expected." +msgstr "%1 is 'n gids, maar het 'n lêer verwag." + +#: core/job_error.cpp:67 +#, kde-format +msgid "%1 is a file, but a folder was expected." +msgstr "%1 is 'n lêer, maar het 'n gids verwag." + +#: core/job_error.cpp:70 +#, kde-format +msgid "The file or folder %1 does not exist." +msgstr "Die lêer of gids %1 bestaan nie." + +#: core/job_error.cpp:73 +#, kde-format +msgid "A file named %1 already exists." +msgstr "'n lêer genaamd %1 alreeds bestaan." + +#: core/job_error.cpp:76 +#, kde-format +msgid "A folder named %1 already exists." +msgstr "'n lêer genaamd %1 bestaan alreeds." + +#: core/job_error.cpp:79 +msgid "No hostname specified." +msgstr "Geen bediener gespesifiseer." + +#: core/job_error.cpp:79 +#, kde-format +msgid "Unknown host %1" +msgstr "Onbekende bediener %1" + +#: core/job_error.cpp:82 +#, kde-format +msgid "Access denied to %1." +msgstr "Toegang na %1 geweier." + +#: core/job_error.cpp:85 +#, kde-format +msgid "" +"Access denied.\n" +"Could not write to %1." +msgstr "" +"Toegang geweier.\n" +"Kon nie na %1 skryf nie." + +#: core/job_error.cpp:88 +#, kde-format +msgid "Could not enter folder %1." +msgstr "Kon nie toegang tot gids %1 verkry nie" + +#: core/job_error.cpp:91 +#, kde-format +msgid "The protocol %1 does not implement a folder service." +msgstr "Die protokol %1 implementeer nie 'n gids diens nie." + +#: core/job_error.cpp:94 +#, kde-format +msgid "Found a cyclic link in %1." +msgstr "Het 'n sikliese skakel in %1 gevind" + +#: core/job_error.cpp:100 +#, kde-format +msgid "Found a cyclic link while copying %1." +msgstr "Het 'n siklies skakel gevind terwyl besig was om %1 te kopiëer." + +#: core/job_error.cpp:103 +#, kde-format +msgid "Could not create socket for accessing %1." +msgstr "Kon nie 'n koppelvlak (socket) skep om toegang tot %1 te verkry nie." + +#: core/job_error.cpp:106 +#, kde-format +msgid "Could not connect to host %1." +msgstr "Kon nie aan bediener %1 koppel nie." + +#: core/job_error.cpp:109 +#, kde-format +msgid "Connection to host %1 is broken." +msgstr "Verbinding na bediener %1 is gebreek." + +#: core/job_error.cpp:112 +#, kde-format +msgid "The protocol %1 is not a filter protocol." +msgstr "Die protokol %1 is nie 'n filter protokol nie" + +#: core/job_error.cpp:115 +#, kde-format +msgid "" +"Could not mount device.\n" +"The reported error was:\n" +"%1" +msgstr "" +"Kon nie mount toestel.\n" +"Die raporteer fout was:\n" +"%1" + +#: core/job_error.cpp:118 +#, kde-format +msgid "" +"Could not unmount device.\n" +"The reported error was:\n" +"%1" +msgstr "" +"Kon nie ontkoppel toestel.\n" +"Die raporteer fout was:\n" +"%1" + +#: core/job_error.cpp:121 +#, kde-format +msgid "Could not read file %1." +msgstr "Kon nie lêer %1 lees nie." + +#: core/job_error.cpp:124 +#, kde-format +msgid "Could not write to file %1." +msgstr "Kon nie na lêer %1 skryf nie." + +#: core/job_error.cpp:127 +#, kde-format +msgid "Could not bind %1." +msgstr "Kon nie aan %1 bind nie." + +#: core/job_error.cpp:130 +#, kde-format +msgid "Could not listen %1." +msgstr "Kon nie na %1 luister nie." + +#: core/job_error.cpp:133 +#, kde-format +msgid "Could not accept %1." +msgstr "Kon nie %1 aanvaar nie." + +#: core/job_error.cpp:139 +#, kde-format +msgid "Could not access %1." +msgstr "Kon nie toegang tot %1 verkry nie." + +#: core/job_error.cpp:142 +#, kde-format +msgid "Could not terminate listing %1." +msgstr "Kon nie ophou luister na %1 nie." + +#: core/job_error.cpp:145 +#, kde-format +msgid "Could not make folder %1." +msgstr "Kon nie gids %1 skep nie" + +#: core/job_error.cpp:148 +#, kde-format +msgid "Could not remove folder %1." +msgstr "Kon nie gids %1 verwyder nie" + +#: core/job_error.cpp:151 +#, kde-format +msgid "Could not resume file %1." +msgstr "Kon nie lêer %1 hervat nie." + +#: core/job_error.cpp:154 +#, kde-format +msgid "Could not rename file %1." +msgstr "Kon nie lêer %1 herbenoem nie." + +#: core/job_error.cpp:157 +#, kde-format +msgid "Could not change permissions for %1." +msgstr "Kon nie regte vir %1 verander nie." + +#: core/job_error.cpp:160 +#, fuzzy, kde-format +#| msgid "Could not change permissions for %1." +msgid "Could not change ownership for %1." +msgstr "Kon nie regte vir %1 verander nie." + +#: core/job_error.cpp:163 +#, kde-format +msgid "Could not delete file %1." +msgstr "Kon nie lêer %1 uitvee nie." + +#: core/job_error.cpp:166 +#, kde-format +msgid "The process for the %1 protocol died unexpectedly." +msgstr "Die proses vir Die %1 protokol dood gegaan onverwags." + +#: core/job_error.cpp:169 +#, kde-format +msgid "" +"Error. Out of memory.\n" +"%1" +msgstr "" +"Fout. Te min geheue.\n" +"%1" + +#: core/job_error.cpp:172 +#, kde-format +msgid "" +"Unknown proxy host\n" +"%1" +msgstr "" +"Onbekende volmag bediener\n" +"%1" + +#: core/job_error.cpp:175 +#, kde-format +msgid "Authorization failed, %1 authentication not supported" +msgstr "Geldigheidstoets gevaal, %1 geldigheidsverklaring nie ondersteunde" + +#: core/job_error.cpp:178 +#, kde-format +msgid "" +"User canceled action\n" +"%1" +msgstr "" +"Gebruiker gekanseleer aksie\n" +"%1" + +#: core/job_error.cpp:181 +#, kde-format +msgid "" +"Internal error in server\n" +"%1" +msgstr "" +"Intern fout in bediener\n" +"%1" + +#: core/job_error.cpp:184 +#, kde-format +msgid "" +"Timeout on server\n" +"%1" +msgstr "" +"Tydverstreke op bediener\n" +"%1" + +#: core/job_error.cpp:187 +#, kde-format +msgid "" +"Unknown error\n" +"%1" +msgstr "" +"Onbekende fout\n" +"%1" + +#: core/job_error.cpp:190 +#, kde-format +msgid "" +"Unknown interrupt\n" +"%1" +msgstr "" +"Onbekende interrupt\n" +"%1" + +#: core/job_error.cpp:201 +#, kde-format +msgid "" +"Could not delete original file %1.\n" +"Please check permissions." +msgstr "" +"Kon nie uitvee oorspronklike lêer %1.\n" +"Asseblief bevestig regte." + +#: core/job_error.cpp:204 +#, kde-format +msgid "" +"Could not delete partial file %1.\n" +"Please check permissions." +msgstr "" +"Kon nie uitvee gedeeltelike lêer %1.\n" +"Asseblief bevestig regte." + +#: core/job_error.cpp:207 +#, kde-format +msgid "" +"Could not rename original file %1.\n" +"Please check permissions." +msgstr "" +"Kon nie oorspronklike lêer %1 herbenoem nie.\n" +"Bevestig asseblief die regte." + +#: core/job_error.cpp:210 +#, kde-format +msgid "" +"Could not rename partial file %1.\n" +"Please check permissions." +msgstr "" +"Kon nie herbenaam gedeeltelike lêer %1.\n" +"Asseblief bevestig regte." + +#: core/job_error.cpp:213 +#, kde-format +msgid "" +"Could not create symlink %1.\n" +"Please check permissions." +msgstr "" +"Kon nie skep simboliese skakel %1.\n" +"Asseblief bevestig regte." + +#: core/job_error.cpp:219 +#, kde-format +msgid "" +"Could not write file %1.\n" +"Disk full." +msgstr "" +"Kon nie skryf lêer %1.\n" +"Disket volgrote." + +#: core/job_error.cpp:222 +#, kde-format +msgid "" +"The source and destination are the same file.\n" +"%1" +msgstr "" +"Die bron en bestemming word Die selfde lêer.\n" +"%1" + +#: core/job_error.cpp:228 +#, kde-format +msgid "%1 is required by the server, but is not available." +msgstr "%1 is benodig deur die bediener, maar is nie beskikbaar." + +#: core/job_error.cpp:231 +msgid "Access to restricted port in POST denied." +msgstr "Toegang na beperkte poort in POST geweier." + +#: core/job_error.cpp:234 +msgid "" +"The required content size information was not provided for a POST operation." +msgstr "" + +#: core/job_error.cpp:237 +#, fuzzy +#| msgid "The file or folder %1 does not exist." +msgid "A file or folder cannot be dropped onto itself" +msgstr "Die lêer of gids %1 bestaan nie." + +#: core/job_error.cpp:240 +#, fuzzy +#| msgid "The file or folder %1 does not exist." +msgid "A folder cannot be moved into itself" +msgstr "Die lêer of gids %1 bestaan nie." + +#: core/job_error.cpp:243 +msgid "Communication with the local password server failed" +msgstr "" + +#: core/job_error.cpp:246 +#, kde-format +msgid "" +"Unknown error code %1\n" +"%2\n" +"Please send a full bug report at http://bugs.kde.org." +msgstr "" +"Onbekende fout kode %1\n" +"%2\n" +"Asseblief stuur 'n volgrote fout raporteer na http://bugs.kde.org." + +#: core/job_error.cpp:271 +#, fuzzy +#| msgid "(unknown)" +msgctxt "@info url" +msgid "(unknown)" +msgstr "(onbekend)" + +#: core/job_error.cpp:278 +#, kde-format +msgctxt "@info %1 error name, %2 description" +msgid "

%1

%2

" +msgstr "" + +#: core/job_error.cpp:282 +msgid "Technical reason: " +msgstr "Tegniese rede: " + +#: core/job_error.cpp:284 +#, fuzzy +#| msgid "

Details of the request:" +msgid "Details of the request:" +msgstr "

Details van die versoek:" + +#: core/job_error.cpp:285 +#, fuzzy, kde-format +#| msgid "

  • URL: %1
  • " +msgid "
  • URL: %1
  • " +msgstr "

    • URL: %1
    • " + +#: core/job_error.cpp:287 +#, kde-format +msgid "
    • Protocol: %1
    • " +msgstr "
    • Protokol: %1
    • " + +#: core/job_error.cpp:289 +#, kde-format +msgid "
    • Date and time: %1
    • " +msgstr "
    • Datum en tyd: %1
    • " + +#: core/job_error.cpp:290 +#, fuzzy, kde-format +#| msgid "
    • Additional information: %1
    " +msgid "
  • Additional information: %1
  • " +msgstr "
  • Aditionele informasie: %1
" + +#: core/job_error.cpp:293 +#, fuzzy +#| msgid "

Possible causes: